The UI is where you develop every query, and the API is how Grafana, scripts and tests run them.
The Prometheus server ships a web UI on port 9090 (rewritten in React for 3.0). The Query page is the expression browser: type PromQL, press Enter, and switch between Table (an instant query at one moment) and Graph (a range query over a window). The metrics explorer button lists every metric name with its help text, and the Explain tab breaks a query into its steps. The Status menu shows targets, service discovery, rules, configuration, flags and TSDB statistics.
Everything the UI does goes through the HTTP API under /api/v1/. Two endpoints matter most:
GET /api/v1/query?query=<expr>&time=<rfc3339 or unix>: an instant query, result typevector,scalarorstring. Omittimefor now.GET /api/v1/query_range?query=<expr>&start=..&end=..&step=15s: evaluates the expression at every step and returns amatrixwithvalues: [[ts, "value"], ...]per series. Values are strings to preserve precision.
Both accept POST with form parameters, which avoids URL-length limits for long queries. Responses are JSON with status: "success" or "error" plus errorType and error.
Other useful endpoints: /api/v1/series?match[]=up (which series exist), /api/v1/labels and /api/v1/label/job/values (for building dropdowns), /api/v1/targets, /api/v1/rules, /api/v1/alerts and /api/v1/status/tsdb. Liveness and readiness are /-/healthy and /-/ready.
The API is read-only unless you enable the admin API, and it has no authentication of its own.
Example
# Instant query (now)
curl -s 'http://localhost:9090/api/v1/query' --data-urlencode 'query=sum(up) by (job)'
# {"status":"success","data":{"resultType":"vector",
# "result":[{"metric":{"job":"api"},"value":[1760700000.123,"2"]}]}}
# Range query: last hour, one point per minute
curl -s 'http://localhost:9090/api/v1/query_range' \
--data-urlencode 'query=rate(http_requests_total{job="api"}[5m])' \
--data-urlencode "start=$(date -u -d '-1 hour' +%s)" \
--data-urlencode "end=$(date -u +%s)" --data-urlencode 'step=60s' | jq '.data.result[0].values[:2]'
# [[1760696400,"12.4"],[1760696460,"12.9"]]
# Which label values exist?
curl -s 'http://localhost:9090/api/v1/label/job/values'
# {"status":"success","data":["api","node","prometheus"]}Remember
- Table = instant query, Graph = range query in the UI
- /api/v1/query returns a vector; /query_range returns a matrix with step
- POST form data for long queries; values come back as strings
Watch out for
- Using a step much smaller than the scrape interval in query_range, which only repeats the same samples.