Skip to content

Batch reads

POST /api/v1/metrics/batch resolves up to 50 metrics in one request. Use it for dashboards, reports, or anywhere you'd otherwise fan out 10+ parallel GET calls.

Why batch

Two reasons matter beyond the obvious "fewer round trips":

  1. One consistent asOf — every query in the batch shares one reference date, so the numbers describe one moment in time.
  2. Cross-query memoization — the registry caches resolved values per request. When two queries depend on the same helper (e.g. revenue.total and expenses.total both use the P&L), it computes once.

Request

json
POST /api/v1/metrics/batch
{
  "asOf": "2024-12-01",
  "queries": [
    { "key": "rev",    "id": "revenue.total",            "period": "ytd" },
    { "key": "cash",   "id": "cash.balance",             "period": "today" },
    { "key": "people", "id": "headcount.active",         "period": "today" },
    { "key": "paygap", "id": "paygap.gender_median_gap", "period": "today" }
  ]
}
FieldRequiredNotes
queriesYesArray of 1–50 { key, id, period } items
queries[].keyYesCaller-supplied identifier. Must be unique within the batch
queries[].idYesMetric id from the reference
queries[].periodYesPeriod string — same syntax as ?period=
asOfNoISO date or today. Pins all relative periods

Response

json
{
  "results": {
    "rev":    { "ok": true,  "data": { "kind": "scalar", "value": 49912.40, "currency": "NZD" }, "meta": { "metric": "revenue.total", "period": {  } } },
    "cash":   { "ok": true,  "data": { "kind": "scalar", "value": 515612.89, "currency": "NZD" }, "meta": {  } },
    "people": { "ok": true,  "data": { "kind": "scalar", "value": 18 }, "meta": {  } },
    "paygap": { "ok": false, "error": { "code": "FORBIDDEN_CAPABILITY", "message": "…", "detail": { "capability": "people.view_paygap" } } }
  },
  "meta": {
    "asOf": "2024-12-01",
    "baseCurrency": "NZD",
    "computedAt": "2026-05-07T03:14:22Z",
    "version": 1
  }
}

A failing query produces { ok: false, error }. Sibling queries are unaffected — the batch never short-circuits.

Limits

FieldLimit
Max queries per batch50
Max body size100 KB (default Next.js handler limit)
Per-batch timeoutNone enforced server-side; client should set its own

For batches larger than 50, split client-side and merge.

Worked example: pinning a dashboard to a past date

js
const res = await fetch("/api/v1/metrics/batch", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${KEY}` },
  body: JSON.stringify({
    asOf: "2024-12-01",
    queries: [
      { key: "rev",      id: "revenue.total",        period: "mtd"   },
      { key: "expenses", id: "expenses.total",       period: "mtd"   },
      { key: "cash",     id: "cash.balance",         period: "today" },
      { key: "ar",       id: "ar.outstanding_total", period: "today" },
      { key: "ap",       id: "ap.outstanding_total", period: "today" },
    ],
  }),
});
const { results } = await res.json();

All five numbers describe the business as it stood on 1 December 2024 — mtd resolves to the November 2024 month-to-date window, snapshots resolve at end-of-day on the pinned date, and the cross-query cache means the underlying P&L computes exactly once.

OneBusiness · Built for businesses that work.