ZerkerGateway

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.

shipped Apache-2.0 /v1/invocations

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.

The full boundary →

Next

Charge for a call.

The x402 gate decides whether a call is allowed to happen before it does.