Observability
Preview The observability resource exposes the
same insights you see in the dashboard — summaries, timeseries, page breakdowns,
logs, traces, resource metrics, and the per-request ledger — as read-only API
endpoints. Most reads are environment-scoped (an env carries its own telemetry
identity); a handful of rollups are account-scoped. Everything here is gated by a
single scope, observability:read, and everything respects the same PII hard-cut and
per-site sampling rules the UI does.
For the data model behind these reads — what a trace, a request row, or web-vitals mean — start with observability overview. This page is the API reference for fetching that data programmatically.
Authorization
Section titled “Authorization”All observability reads require the observability:read scope. write is not a
thing here — observability is read-only over the API.
Tenancy is enforced two ways depending on the route:
- Env-scoped reads load the environment first, so an out-of-scope env returns
404(existence hiding) and a key pinned to a different env never sees it. See errors for the404-vs-403split. - Account-scoped reads (
/v1/usage/storage,/v1/logs,/v1/traces,/v1/metrics) are loaderless — there is no resource in the path to load, so tenancy is resolved inside the handler from your principal. A key’s resource constraint is honored here too: if the key is pinned to a site or team, these rollups are intersected down to only what that key may see.
Environment-scoped insights
Section titled “Environment-scoped insights”All of these live under an environment and return the response
envelope. Collections are
cursor-paginated — pass ?limit= and ?cursor=, read
pagination.next_cursor and pagination.has_more back.
| Method + path | Returns |
|---|---|
GET …/{envID}/insights/summary |
rolled-up totals + p95 latency for a window |
GET …/{envID}/insights/timeseries |
a metric bucketed over time |
GET …/{envID}/insights/pages |
top paths by volume, errors, or latency |
GET …/{envID}/insights/logs |
application log lines |
GET …/{envID}/insights/traces |
request traces (sampled per site/plan) |
GET …/{envID}/insights/traces/{id} |
one trace, span by span |
GET …/{envID}/insights/resources |
CPU / memory / I/O resource metrics |
GET …/{envID}/insights/requests |
the per-request ledger (blocks excluded) |
The full path prefix is /v1/sites/{siteID}/environments/{envID}. Blocked requests
are kept out of insights/requests — they live under the
security resource, exactly as the UI separates
Requests from Security → Blocks.
Live tails (SSE)
Section titled “Live tails (SSE)”Two of these views stream. They hold the connection open and push rows as they happen, over server-sent events:
| Method + path | Streams |
|---|---|
GET …/{envID}/insights/logs/stream |
log lines, live |
GET …/{envID}/insights/requests/stream |
served requests, live |
Common parameters
Section titled “Common parameters”| Name | Type | Required | Description |
|---|---|---|---|
from |
string (RFC 3339) |
no | start of the window; defaults to the env’s retention floor |
to |
string (RFC 3339) |
no | end of the window; defaults to now |
interval |
string |
timeseries | bucket size, e.g. 1m, 5m, 1h |
metric |
string |
timeseries | which series, e.g. requests, p95_latency_ms, error_rate |
status |
string |
no | filter requests/traces to a status class, e.g. 5xx |
limit |
integer |
no | page size for collections |
cursor |
string |
no | opaque pagination cursor |
Account-scoped reads
Section titled “Account-scoped reads”These roll telemetry up across everything your key can see, with no env in the path. They are loaderless: tenancy is resolved in-handler and the key’s resource constraint is intersected in, so a site-pinned key gets a site-pinned rollup.
| Method + path | Returns |
|---|---|
GET /v1/usage/storage |
storage usage across owned sites/envs |
GET /v1/logs |
account-wide log search |
GET /v1/traces |
account-wide trace search |
GET /v1/metrics |
account-wide metric rollups |
Worked example — a latency timeseries
Section titled “Worked example — a latency timeseries”Fetch p95 latency for a production environment, bucketed by five minutes.
curl https://api.managed.dev/v1/sites/site_01J7Q2/environments/env_01J8D9/insights/timeseries \ --get \ --data-urlencode "metric=p95_latency_ms" \ --data-urlencode "interval=5m" \ --data-urlencode "from=2026-06-24T00:00:00Z" \ -H "Authorization: Bearer mfk_live_9aF2…" \ -H "Forge-Version: 2026-06-23"req, _ := http.NewRequest("GET", "https://api.managed.dev/v1/sites/site_01J7Q2/environments/env_01J8D9/insights/timeseries", nil)q := req.URL.Query()q.Set("metric", "p95_latency_ms")q.Set("interval", "5m")q.Set("from", "2026-06-24T00:00:00Z")req.URL.RawQuery = q.Encode()req.Header.Set("Authorization", "Bearer mfk_live_9aF2…")req.Header.Set("Forge-Version", "2026-06-23")
resp, err := http.DefaultClient.Do(req)const url = new URL( "https://api.managed.dev/v1/sites/site_01J7Q2/environments/env_01J8D9/insights/timeseries",);url.search = new URLSearchParams({ metric: "p95_latency_ms", interval: "5m", from: "2026-06-24T00:00:00Z",}).toString();
const res = await fetch(url, { headers: { Authorization: "Bearer mfk_live_9aF2…", "Forge-Version": "2026-06-23", },});mf insights timeseries \ --env env_01J8D9 \ --metric p95_latency_ms \ --interval 5m \ --from 2026-06-24T00:00:00Z{ "data": { "metric": "p95_latency_ms", "interval": "5m", "points": [ { "t": "2026-06-24T00:00:00Z", "value": 142 }, { "t": "2026-06-24T00:05:00Z", "value": 138 }, { "t": "2026-06-24T00:10:00Z", "value": 201 } ] }, "request_id": "req_01J9AB"}Worked example — tail logs live
Section titled “Worked example — tail logs live”Hold a connection open and read application logs as they’re written. The endpoint
streams server-sent events; each
data: frame is one log line in the envelope’s data shape.
curl -N https://api.managed.dev/v1/sites/site_01J7Q2/environments/env_01J8D9/insights/logs/stream \ -H "Authorization: Bearer mfk_live_9aF2…" \ -H "Forge-Version: 2026-06-23" \ -H "Accept: text/event-stream"mf logs --env env_01J8D9 --followevent: logdata: {"ts":"2026-06-24T00:11:03.221Z","level":"error","path":"/checkout/","msg":"db timeout after 5000ms"}
event: logdata: {"ts":"2026-06-24T00:11:03.998Z","level":"info","path":"/checkout/","msg":"retry succeeded"}The requests/stream endpoint works identically — point it at a deploy window
filtered to ?status=5xx to watch for regressions in real time, then
roll back if errors scroll in.