The read API: the same numbers the dashboard shows, for your own pricing pages, admin panels, and investor updates.
curl "https://api.datagauge.dev/v1/metrics/mau?date_from=2026-06-19&date_to=2026-07-18" \
-H "Authorization: Bearer $DATAGAUGE_KEY"Two credentials can read. An sk_live_ key is scoped to one app, so the
project is implied. A dg_live_ control token with the
metrics:read scope is org-scoped and reads any app in the org, so it must
name one: add ?project=proj_.... That is the app's public_id from
GET /v1/apps, the same value the
billing webhook path calls {project_id}, and not the app's app_id.
Public pk_live_ keys can never read.
Both credentials are plane-confined. An sk_test_ key reads its app's test
plane; a dg_test_ token reads only test projects (name one with
?project=<test_public_id>). A dg_live_ token reads only live projects; a
project in the other plane is 404 unknown project, the same as one outside
your org.
| Metric | Returns | Window |
|---|---|---|
mau | distinct active users | inclusive dates, default last 30 days |
dau | daily series [{day, dau}] | same |
signups | distinct users who signed up | same |
revenue | [{day, currency, revenue}], minor units, earned only | same |
volume | [{day, currency, volume}], marketplace gross processed | same |
mrr | [{currency, mrr}], current | windowless |
Distinct counts (mau, dau, signups) are HyperLogLog-precise (~0.5% at
scale) and immune to duplicate events by construction. Money endpoints are
exact.
How far behind ingest these numbers are
A write is not readable here for about five minutes. Two delays stack, and neither is a fault:
| Stage | Delay |
|---|---|
| Ingest batches before it stores. A trickle sender's batch waits out the timeout rather than filling. | up to 30 s |
Metric reads are cached at the edge (x-cache: hit|miss). | 300 s |
The cache is the one that surprises people, because it latches: read a metric one second after writing and you pin the pre-write answer for the next five minutes, however many times you ask.
This matters most when you are learning the reserved
names: someone checking whether payment really needs
props.amount sends one, reads back zero revenue, and cannot tell a rejected
event from a queued one. Read the ingest response instead: a rejected event is
422, or a 202 carrying a non-empty errors[], and it says so at the moment
you send it rather than five minutes later.
If the upstream is rate-limited or unavailable we serve the last known value
rather than fail: x-cache: stale, with x-cache-age giving its age in
seconds.
When a read is refused: 429
Two different ceilings can refuse a read, and both answer 429. They are not the
same event and the right response to them is opposite, so branch on
x-datagauge-throttle rather than on the status code.
x-datagauge-throttle | What happened | What to do |
|---|---|---|
rate_limit | Your plan's per-minute read ceiling. | Retry. Retry-After gives the exact seconds to wait, computed from the oldest request still inside the window, not a fixed backoff. |
allowance:queries | The org's agent-query allowance for the billing period is spent. | Do not retry: it cannot succeed until the period rolls over. x-datagauge-reset is the ISO 8601 instant it returns. |
allowance:raw_scans | The sub-limit on the three metrics that scan raw events (event_names, event_count, event_series) is spent. | Stop asking for those three; x-datagauge-reset again says when they return. Every other metric is still served. |
Retry-After is present on rate_limit and deliberately absent from an
allowance refusal, where the honest value would be days. The body carries the
same facts for a client that parses bodies:
{ "ok": false, "reason": "allowance", "scope": "queries",
"reset_at": "2026-08-01T00:00:00.000Z",
"error": "agent query allowance exhausted for this billing period" }The per-minute ceiling is per organization: an sk_live_ key's traffic and a
dg_live_ token's traffic add up against the same figure, and issuing more
credentials of either kind does not raise it. A cache hit (x-cache: hit) never
consults either limit.
Only dg_live_ reads spend the period allowance; that is the agent surface, and
the MCP server is one of its clients, so a tool call and a direct
token read cost the same thing. sk_live_ reads and your own dashboard never
spend it.
Treat an x-datagauge-throttle value you do not recognize as a bare 429. The
three above are the whole vocabulary; matching a new label by prefix is how a
client ends up retrying something it should have stopped on.
A machine-readable description of the whole API (this read plane plus the
ingest endpoints) lives at
https://datagauge.dev/openapi.json
(OpenAPI 3.1), on the docs site rather than on api.datagauge.dev. The 429 is
described there too, with the header vocabulary as an enum.