Zerker Observability
Every call leaves a record.
The proxy records status, mode, timing, sizes, and upstream status as it forwards. This is the read layer over those records: list them, open one, or aggregate them. Nothing else to run.
Reading it back
Filter first, then open one.
The list endpoint is tenant-scoped, paginated, and filterable by agent, status, mode, error class, model, settlement, and time window.
The failures, last 20
$ curl localhost:8080/v1/invocations\ ?agent_id={id}&status=failed&limit=20 ← bodies are never returned on the list endpoint, at any scope
One record, in full
$ curl localhost:8080/v1/invocations/{id} { "status": "failed", "error_class": "upstream_5xx", "mode": "streaming", "ttft_ms": null, "body_captured": false }
The mechanic
Bodies are opt-in.
Request and response bodies are the most sensitive thing Gateway touches. Reading them requires two separate decisions, usually by two different people.
| Key | What it is | Held by |
|---|---|---|
| Capture | An agent-level toggle, off by default — if it was off, there is nothing to read | Whoever configures the agent |
| Scope | The invocations:read_body OAuth scope. Metadata reads don't need it |
Whoever issues the token |
body_captured is always set. It records whether capture was on for that call, whether or not you can read the bodies. An auditor can tell "nothing was recorded" from "recorded, but not visible to you." Bodies are base64-encoded and capped at 1 MiB.
Three fields
Fields you might miss.
Status, latency, and sizes are expected. These three carry the decisions.
| Field | What it holds | The catch |
|---|---|---|
ttft_ms |
Time to first byte on the streaming path | null for transactional calls, or when no bytes ever streamed |
error_class |
A coarse taxonomy: timeout, upstream_5xx,
upstream_4xx, ssrf_blocked,
credential_error, cancelled, internal
|
null on success. Coarse on purpose: for grouping, not forensics |
model |
Whatever the caller sent in X-Zerker-Model |
Caller-supplied only. Gateway does not read bodies to guess the model. MCP is the exception, because its envelope is a published shape |
Aggregates
Percentiles.
Group by agent, bucket by hour or day, and get count, error rate, a breakdown by error class, and p50/p95/p99 for latency and time to first byte. The limits below are deliberate, and the endpoint reports them instead of degrading quietly.
$ curl localhost:8080/v1/analytics\
?group_by=agent_id&bucket=day&since=2026-07-01T00:00:00Z
| Rule | What happens | Why |
|---|---|---|
since is required |
An unbounded query is 400 |
There is no default window |
| The window caps at 31 days | Wider is 400 |
Percentiles over an unbounded range would let a read endpoint take down the write path |
| Its own rate limit | 429 here only |
Tighter than the general /v1 limiter, and separate from it |
| An empty window | 200 with an empty groups array |
Not 404. "Nothing happened" is an answer, not a missing resource |
| In-flight calls | Counted, but excluded from percentiles | A call that has not finished has no latency yet |
Every query filters by tenant first. An invocation ID belonging to another
tenant returns 404, never 403, and the analytics endpoint
only ever aggregates your own tenant's calls.
Where the line falls
Records are open source. The dashboard is hosted.
Invocation and payment records, readable over the API, are Apache-2.0. The data never leaves your database. The hosted dashboard is what we sell.
| Capability | Tier |
|---|---|
| Raw invocation and payment capture, readable over the API | OSS |
| Aggregate analytics — counts, error rates, latency and TTFT percentiles | OSS |
| Usage and revenue dashboard, quotas, spend limits, invoicing | Commercial |
One gap: Gateway has no OpenTelemetry export yet. Metrics are read from its own API, not pushed to Datadog or Grafana. If you are standardised on OTel, ask us about timing before you build on this.
Next
Charge for a call.
The x402 gate decides whether a call is allowed to happen before it does.