Skip to content

API Reference

This page documents the HTTP API exposed by the local dashboard embedded in every SDK — the same API the bundled dashboard UI calls to render charts and insights. It is useful if you want to pull metrics into your own tooling instead of (or alongside) the embedded UI.

Not the Cloud ingest API

This is the read-only API served by each SDK's own local dashboard process. It has nothing to do with the SaaS ingest endpoint used in Cloud Mode — cloud mode disables the local dashboard entirely.

Base URL

SDKBase URLNotes
Node.jshttp://127.0.0.1:<dashboardPort>Default port 4242. Bound to loopback only — not reachable from other machines.
Pythonhttp://127.0.0.1:<dashboard_port>Default port 4242. Bound to loopback only.
PHP (Laravel)https://your-app.test/<dashboard_prefix>Default prefix _apiforge. Served on the same Laravel app/port — reachable by anyone who can reach your app, unless you restrict the route yourself (e.g. behind auth middleware or your local-only environment).

There is no authentication on these endpoints in Node.js/Python (they only listen on loopback, so this is not a network-exposed risk by default). On PHP, since the dashboard rides on your existing Laravel routes, restrict access yourself if your app is reachable beyond your own machine (e.g. gate the route group behind auth middleware, or disable it in production via dashboard_enabled => false).

Endpoints

GET /

Serves the dashboard UI (single HTML page, assets inlined or self-hosted — see Local Dashboard).

GET /api/summary

Top-level numbers shown on the dashboard's overview screen: health score, last-24h call volume/error rate/latency, route counts, and the full current insights list.

json
{
  "health_score": 87,
  "calls_24h": 48213,
  "error_rate_24h": 0.42,
  "avg_p90_24h": 134.7,
  "avg_p99_24h": 310.2,
  "active_routes": 12,
  "total_routes": 15,
  "insights_count": 2,
  "insights": [ /* Insight[], see below */ ]
}
FieldTypeNotes
health_scorenumber | null0–100. null when there has been no traffic at all. See Health Score.
calls_24hnumberTotal requests in the last 24h (excludes ghost/untracked traffic accounting — see is_ghost under /api/routes).
error_rate_24hnumberPercentage (0–100), 2 decimal places. (4xx + 5xx) / total * 100.
avg_p90_24h / avg_p99_24hnumber | nullAverage P90/P99 latency in ms across all routes over the last 24h.
active_routesnumberDistinct route+method pairs with traffic in the last 24h.
total_routesnumberDistinct route+method pairs ever seen.
insights_countnumberinsights.length, provided for convenience.
insightsInsight[]Same shape as GET /api/insights (Python) — see Insight object.

GET /api/routes

Per-route aggregated stats, one row per (route, method) pair, for the requested time window.

Query params: hours (optional, default 24) — lookback window.

json
[
  {
    "route": "GET /users/:id",
    "method": "GET",
    "is_ghost": 0,
    "calls": 1024,
    "calls_2xx": 1000,
    "calls_3xx": 10,
    "calls_4xx": 12,
    "calls_5xx": 2,
    "p50": 45.2,
    "p90": 134.7,
    "p99": 310.2,
    "lat_max": 890.1,
    "bytes_avg": 2048.0,
    "request_size_avg": 128.0,
    "inflight_avg": 3.1,
    "inflight_max": 9
  }
]

Routes declared in your app but never called (see Insights → UNTRACKED) are appended to this list with untracked: true and all metric fields null/0.

Results are capped at 100 rows, ghost routes last, sorted by call volume descending.

GET /api/timeseries

Time-bucketed stats (one point per minute) for a single route+method, for charting.

Query params: route (required), method (required), hours (optional, default 24).

json
[
  { "bucket_ts": 1751800020, "calls": 42, "p50": 40.1, "p90": 120.4, "p99": 280.0, "errors": 1, "redirects": 0 }
]

SDK inconsistency

On Node.js and PHP, omitting route or method returns 400 { "error": "route and method are required" }. On Python, omitting them silently falls back to the same data as /api/global-timeseries instead of erroring — this divergence hasn't been reconciled yet, don't rely on the fallback behavior.

GET /api/global-timeseries

Same shape as /api/timeseries, aggregated across all routes (no route/method filter).

Query params: hours (optional, default 24).

json
[
  { "bucket_ts": 1751800020, "calls": 512, "p50": 38.0, "p90": 110.2, "p99": 260.5, "errors": 3 }
]

GET /api/releases

Known deploys (from the release config option), most recent first, capped at 20.

json
[
  { "release_tag": "v1.4.0", "release_ts": 1751800020, "routes_affected": 12 }
]

GET /api/insights Python only

Returns the Insight[] array alone (same content as the insights field of /api/summary). Node.js and PHP don't expose this as a separate endpoint today — use /api/summary on those SDKs.

Insight object

Every entry in an Insight[] array (from /api/summary or /api/insights) has the same shape, regardless of type:

json
{
  "type": "PERF",
  "severity": "error",
  "route": "/users/:id",
  "method": "GET",
  "message": "`GET /users/:id` P90 increased by 34% after v1.4.0. Before: 90ms — After: 121ms.",
  "data": { "release": "v1.4.0", "before_p90": 90, "after_p90": 121, "delta_pct": 34.4 }
}
FieldTypeNotes
typestringOne of PERF, OK, DEAD, ANOMALY, DRIFT, UNTRACKED. See Automatic Insights for what triggers each one.
severitystringsuccess | info | warning | error — drives the color/icon in the UI.
route / methodstringThe endpoint the insight is about.
messagestringHuman-readable, ready to display as-is.
dataobjectShape depends on type — see below.

data shape per type

Typedata fields
PERF / OKrelease, before_p90, after_p90, delta_pct
ANOMALYcurrent_p99, baseline_p99, z_score
DEADlast_seen_ts, inactive_days
DRIFTslope_ms_per_day, observed_days, projection_30d_ms
UNTRACKEDfirst_seen_ts

Errors

All endpoints return plain-text 404 for unknown paths. /api/timeseries is the only endpoint with a documented 400 (see above). No endpoint returns 5xx under normal operation — internal errors are caught and degrade to empty results rather than propagating (consistent with the SDKs never being allowed to crash the host app).

Released under the MIT License.