Platform stats

The venue’s own measured aggregates — traded volume, open interest, market counts — as two public JSON reads, with every figure carrying the timestamp of the measurement behind it.

The stats page is for humans. The same numbers are available as two public endpoints, so a dashboard or a tracker can read Drazill’s aggregates without scraping a page.

These endpoints are behind a feature flag and are not switched on yet. While PUBLIC_STATS_ENABLED is off they return 404, in every environment including production. They are documented here so the contract is stable before the flip, not because they are currently serving. Nothing below describes a figure that does not exist — every example is labelled as an example.

The two reads

PathReturns
GET /api/v1/statsWindowed totals, the most recent measured day, and right-now market counts.
GET /api/v1/stats/dailyA bounded daily series, newest last. ?days=N, 1–400, default 30.

Both are public — no API key, no session — and rate-limited at 60 requests per minute per client. Both send Cache-Control: public, max-age=60, stale-while-revalidate=300 and a weak ETag; send it back as If-None-Match and you will get a 304 instead of a body.

An API key may read them too, under the data:read scope. There is no live/test split: one set of published venue numbers, not a sandbox copy.

Freshness: two clocks, stated separately

The response does not have a single “as of”. It has one per block, because the blocks are measured differently and pretending otherwise would misdate half the payload.

  • totals and latest_day come from an analytics job that rebuilds overnight. Their as_of is the moment that job last completed successfully — so if last night’s build failed, you will see the previous night’s timestamp rather than a fresh-looking one.
  • live is read at request time. Its as_of really is “now”.

basis on each block names the source, which also explains the one number that legitimately appears twice with different values: live.markets_active counts markets open for trading at that instant, while the daily series’ markets_active counts markets that were tradable at some point during that day.

”Totals” are windowed, and say so

There is no all-time figure on this surface, and that is deliberate. The analytics job prunes its own history to a retention horizon, so a sum over what it holds is “since the oldest retained day” — a figure that would silently become a rolling window the moment the venue outlives its retention, while still being labelled lifetime. So totals carries window_start_dt, the oldest day the sums actually cover, and every field is documented as “since window_start_dt”.

What is absent, and why it is not zero

A measurement Drazill has not taken is returned as null, never as 0.

  • When the nightly job has never completed, every figure in totals is null and empty_reason is "mart_never_built". When it has run but has no row in the window, empty_reason is "no_rows_in_range".
  • unique_traders is withheld — null, with unique_traders_suppressed set to "below_k_anonymity_floor" — on any day whose trader count falls under the platform’s minimum reporting threshold. The count itself is not returned in banded form either; a band beside the neighbouring days would leak the same individual.
  • There is no cumulative unique_traders, deliberately. It is a per-day distinct count, so summing it would count a returning trader once for every day they traded. Rather than publish an inflated figure, the API publishes none.
  • The live block reports empty_reason: "projection_unbacked" with null counts when the market projection it reads holds no rows at all. “Not built in this environment” and “no markets are open” are different facts, and only the second is a 0.

open_interest_cents and markets_active are levels, not flows: the value at the close of a day, not that day’s change. Do not add them across rows.

Units

Money is integer cents on every field ending _cents, in Canadian dollars. Counts are plain integers. Dates are YYYY-MM-DD UTC calendar days; timestamps are ISO-8601.

Example response

The values below are illustrative, not real figures — they show the shape of the payload, nothing about Drazill’s actual volume.

GET /api/v1/stats (example shape)
{
"environment": "production",
"data_basis": "measured",
"generated_at": "2026-01-15T12:00:00+00:00",
"totals": {
"basis": "agg_platform_daily",
"as_of": "2026-01-15T04:52:00+00:00",
"empty_reason": null,
"window_start_dt": "2025-12-12",
"volume_cents": 0,
"trade_count": 0,
"order_count": 0,
"markets_resolved": 0,
"open_interest_cents": 0,
"open_interest_dt": "2026-01-14"
},
"latest_day": {
"dt": "2026-01-14",
"as_of": "2026-01-15T04:52:00+00:00",
"volume_cents": 0,
"trade_count": 0,
"unique_traders": null,
"unique_traders_suppressed": "below_k_anonymity_floor"
},
"live": {
"basis": "market_card_projections",
"as_of": "2026-01-15T12:00:00+00:00",
"empty_reason": null,
"markets_active": 0,
"volume_24h_cents": 0
}
}

The environment field

Every response names the environment that produced it. Anything other than "production" is a non-production deployment holding test data, and a client rendering these numbers should label them as such. The human page does exactly this, off this field rather than off a build-time constant — so a production build pointed at a staging API still labels the data correctly.

Aggregates only

These endpoints are platform-wide by construction. There is no user, market, or segment dimension in the query or in any response field, so there is no per-entity data to request and no drill-down parameter to pass.