Periods and ?asOf=
Every metric value is computed over a period — a range like last_fy, a month, or a point-in-time snapshot like "cash balance today". The API supports two query parameters:
?period=…— required. Tells the registry which window to compute over.?asOf=…— optional. Tells the registry what date to treat as "today", so all relative periods resolve historically.
?period=
Three forms are accepted.
Relative keys
| Key | Meaning |
|---|---|
mtd | Month to date |
qtd | Quarter to date |
ytd | Year to date (FY-aligned) |
this_month | The current calendar month |
last_month | The previous calendar month |
this_quarter | The current FY quarter |
last_quarter | The previous FY quarter |
this_fy | The current financial year |
last_fy | The previous financial year |
last_2_fy | The two FYs before this one |
today | Snapshot — a point in time (use with asOf-style metrics like cash.balance) |
Trailing windows: trailing_3, trailing_6, trailing_12 (any positive integer is accepted).
Absolute ISO range
?period=2024-04-01:2025-03-31Both bounds are inclusive. Start must be on or before end, both must be valid YYYY-MM-DD.
Period kinds
The registry distinguishes range metrics (revenue, expenses, P&L totals) from snapshot metrics (cash balance, headcount, AR aging). Each metric declares which kind it accepts:
{ "id": "cash.balance", "periodKind": "snapshot" }
{ "id": "revenue.total", "periodKind": "range" }Pass an incompatible period and you'll get 400 PERIOD_KIND_MISMATCH. Use today (or another snapshot key) for snapshot metrics; use mtd / last_fy / ISO range for range metrics.
?asOf=
Pin all relative period resolution to a historical date. Two acceptable values:
today(or omitted) — resolves against the current moment- ISO date
YYYY-MM-DD— resolves as if today were that date
# Today's MTD revenue
curl ".../metrics/revenue.total/value?period=mtd"
# MTD revenue as it stood on 1 December 2024
curl ".../metrics/revenue.total/value?period=mtd&asOf=2024-12-01"Both calls hit the same metric with the same period spec. The asOf parameter rewrites what "today" means for the duration of that single request — so mtd, qtd, ytd, last_fy, trailing_12 and friends all resolve relative to the pinned date.
Snapshot metrics ignore asOf
If the period itself is fully self-contained (e.g. ?period=as_of:2024-12-01 for snapshot metrics), asOf has no effect. The period date wins.
Future dates are rejected
?asOf=2099-01-01{ "error": { "code": "INVALID_AS_OF",
"message": "asOf cannot be in the future: '2099-01-01'. We don't fabricate forward-looking values." } }Worked example
A dashboard wants to render "what did the business look like on 1 December 2024?" Make one batch request with asOf at the top level — every metric in the batch shares the same reference date, so the dashboard is internally consistent:
POST /api/v1/metrics/batch
{
"asOf": "2024-12-01",
"queries": [
{ "key": "rev", "id": "revenue.total", "period": "ytd" },
{ "key": "expense","id": "expenses.total", "period": "ytd" },
{ "key": "cash", "id": "cash.balance", "period": "today" },
{ "key": "people", "id": "headcount.active", "period": "today" }
]
}rev and expense resolve their YTD windows against the pinned date; cash and people snapshot at the same date. All four numbers describe one consistent moment in time.