Agent stats API: gateway decision analytics for agents
On this page
Reference for operators wiring an AI agent or automation into the gateway’s decision-log analytics. Covers: what the surface is, how to set it up, the seven tools and REST routes, behavior under edge conditions, and bearer-token management.
What it is#
The agent stats API gives an AI agent (or any HTTP client) read-only access
to the gateway’s decision-log analytics, without touching the dashboard.
Numbers are computed by SQL directly over the frame-validated decision log;
the model receives structured data and narrates from it. Every aggregate is
traceable: the decisions tool and /api/v1/decisions endpoint return the
individual receipts (timestamp, verdict, repo, refs, top findings) that
back any summary, so an agent can show its work.
The surface has two transports: an MCP endpoint for tool-calling agents, and a REST endpoint for scripts and custom integrations. Both share the same data and the same bearer-token auth.
Setup#
1. Mint a token.
nimblegate gateway token new <label>
The token is printed once and stored as a SHA-256 hash in _auth.db.
Copy it: it cannot be retrieved again.
2. Point your MCP client at the gateway.
Add the gateway as an MCP server using HTTP+JSON-RPC 2.0 (no SSE):
URL: http://<host>:7900/mcp
Header: Authorization: Bearer <token>
Most MCP clients accept a headers map in their server config. The gateway exposes seven tools (see The tools below).
3. Or use REST directly.
curl -s -H "Authorization: Bearer nbg_..." \
"http://127.0.0.1:7900/api/v1/bounce-rate?days=30&min=5"
All routes live under /api/v1/ and accept the same query parameters
as the MCP tool arguments (see REST reference).
The tools#
| Tool | What it answers | Notable args |
|---|---|---|
gate_stats |
Overall decision counts, accept/reject breakdown, per-repo activity, and the top firing rules for the period | repo, days |
bounce_rate |
Per-repo ratio of rejected pushes to total pushes (which repos push-and-bounce the most) | repo, days, min_pushes (default 5; omits repos below the threshold) |
top_rules |
Which frames fired most often, with finding counts | repo, days, severity |
recurring_findings |
Findings that keep coming back across multiple pushes from the same repo (candidates for fixing at the source or whitelisting with a reason) | repo, days, limit |
decisions |
Individual push receipts: timestamp, verdict, repo, refs, and top findings (the raw evidence behind any aggregate) | repo, days, result (accepted|rejected), limit |
time_saved |
Estimated debugging hours the gate prevented: distinct blocking findings × per-frame hours-per-hit (actual = blocked-and-fixed; modeled = conservative upper bound). Same math as the dashboard’s Time saved tab | repo, days, limit |
what_changed |
Recent commits in a repo (or all repos): what changed, where to look, and the gate’s verdict on each pushed tip. Rejected pushes don’t advance refs, so the tag is normally “accepted” (with any warnings that rode along) | repo, days, path, query, limit |
All tools accept:
repo(string, optional): filter to one repo; omitting covers all repos.days(int, optional, default 30, max 365): lookback window in days. Out-of-range values clamp with a note in the response.severity(string, optional,top_rulesonly): one ofBLOCK,ERROR,WARN,INFO; unknown values are ignored with a note. Other tools accept but ignore it.result(string, optional,decisionsonly):acceptedorrejected.min_pushes(int, optional, default 5,bounce_rateonly): minimum push count to include a repo; filters out repos with too little activity to produce a meaningful rate.limit(int, optional, default 10, max 50,recurring_findings,decisions, andtime_saved): row cap on list results (recurring findings, decision receipts, or per-frame ROI rows). The top-firing-rules list is capped at 20 internally, shared by bothtop_rulesandgate_stats(their “top frames” come from the same query).path(string, optional,what_changedonly): limit to commits touching this path.query(string, optional,what_changedonly): keyword to grep commit messages.format(string, optional, MCP only):text(default, narrated lines) orjson(the structured{data, notes}envelope as the result text). Passjsonwhen an agent needs to compute or correlate on the numbers rather than narrate them: the model gets structured data instead of prose. (REST always returns the JSON envelope, soformatis a no-op there.)
Tool results are labeled text with a parenthetical period header, e.g.
(gateway decision log, last 30 days), unless format=json, which returns
the structured envelope instead.
REST reference#
Routes#
| Method | Path | Tool equivalent |
|---|---|---|
GET |
/api/v1/stats |
gate_stats |
GET |
/api/v1/bounce-rate |
bounce_rate |
GET |
/api/v1/top-rules |
top_rules |
GET |
/api/v1/recurring |
recurring_findings |
GET |
/api/v1/decisions |
decisions |
GET |
/api/v1/time-saved |
time_saved |
GET |
/api/v1/changes |
what_changed |
Query parameters mirror the tool arguments: repo, days, severity,
result, min (maps to min_pushes), limit, path, query.
Example#
curl -s \
-H "Authorization: Bearer nbg_..." \
"http://127.0.0.1:7900/api/v1/bounce-rate?days=30&min=5"
Response envelope#
Every response is a JSON object with two keys:
{
"data": { ... },
"notes": ["repo myapp not found - answered for all repos (known: api, frontend)", "days clamped to 365"]
}
data carries the result payload (structure varies per route).
notes is an array of informational strings, omitted when there is
nothing to report. Callers should surface notes alongside results;
they carry information the agent needs to give an accurate answer.
Behavior notes#
- Browser clients (CORS): both endpoints answer preflight
OPTIONSand send permissive CORS headers, so browser-based MCP clients like the llama.cpp webui connect directly. In the webui’s header fields use nameAuthorization, valueBearer nbg_...; the wordBearerbelongs in the value. Safe to allow any origin: auth is the bearer header, never cookies.
Parameter clamping. Out-of-range values for days (max 365) and limit
(max 50) are silently clamped to the allowed maximum. The clamp is reported
in the notes array so the caller knows the effective value used.
Unknown repo recovery. Requesting a repo that the gateway does not
recognise does not return an error. Instead the query runs across all repos
and the notes array carries a message of the form:
"repo X not found - answered for all repos (known: ...)".
This lets an agent that misremembers a repo name still get a useful answer.
Observe-mode banner. When a per-repo query targets a repo configured
observe = true, the notes array leads with
"⚠ OBSERVE MODE - <repo> records findings but NEVER blocks pushes ...".
Observe mode is silent to the pushing client by design, so this report-side
banner is the operator’s signal that the gate is advisory-only for that repo.
A BLOCK-severity finding was recorded but the push was relayed, not
rejected. The flag is read fresh from the repo’s gateway.toml per request.
See Observe or enforce.
Rate limiting. The agent API enforces 60 requests per minute per token.
Requests over the limit receive a 429 Too Many Requests response with a
Retry-After header indicating how long to wait before retrying.
Ingest staleness. The analytics index refreshes at most every 5 seconds.
If a refresh fails (database contention, I/O error), the API continues
serving the last successfully computed data and adds
"analytics refresh failed - serving existing data" to the notes array.
Stale data is always better than an error; the note tells the caller.
Excerpt redaction. Finding excerpt text (the matched line from the
pushed file) is not included in agent API responses. A dashboard flag
--agent-api-excerpts exists but defaults to false and is reserved for
future receipts enrichment; do not rely on excerpt text being present in
the current release.
--auth=off. When the gateway is started with --auth=off, no token
verifier is wired, so the agent API is disabled and every request returns
503 Service Unavailable. (Tokens are SHA-256-hashed in _auth.db; with auth
off there is no verifier to check a presented token against; it’s the missing
verifier, not a missing file, that triggers the 503.)
Token management#
Tokens are managed via the nimblegate gateway token subcommand. Flags
--policy-root (default /srv/gateway/cfg) and --auth-db
are accepted in any position.
Mint a new token (label is a human-readable name for your records):
nimblegate gateway token new <label>
The full token is printed once. It is stored SHA-256-hashed in _auth.db
and cannot be retrieved again; store it securely at mint time.
List active tokens:
nimblegate gateway token list
Shows token IDs, labels, and creation timestamps. Token values are not shown.
Revoke a token by ID:
nimblegate gateway token revoke <id>
Revocation takes effect immediately. The token ID comes from token list.