Skip to main content

Usage

The Usage API surfaces the same trace-count meter that powers the in-product usage bars and the Stripe overage line items: a per-month trace count read from DecisionTrace, weighed against the organisation's plan limit, with month-over-month and projected-monthly helpers attached. Tested against API version v1. JWT auth on every endpoint — no API key path, no plan-gate: every paid organisation can read its own counter, including the Sandbox tier that gets hard-blocked at the limit. Read-only.

How the meter works

The counter is the DecisionTrace collection itself; there is no secondary meter to drift away from. GET /current runs four countDocuments() queries in parallel against the org's trace collection (this-month / last-month / today / all-time) and computes the derived fields in-process. Plan limits are read from Organization.billing.plan:

PlanMonthly trace limitEnforcement
sandbox10,000Hard block (429 USAGE_LIMIT_EXCEEDED)
scale100,000Soft — overage metered to Stripe
governance500,000Soft — overage metered to Stripe
enterprise2,000,000Soft — overage metered to Stripe
customUnlimitedCounter still returned; unlimited: true

A separate 60-second in-memory cache protects the trace-ingestion hot path from countDocuments() pressure — the count read on every POST /traces would otherwise dominate the p99 latency budget. The dashboard read path on this resource is not cached and always returns the live count, which is why a freshly-ingested trace shows up here within milliseconds while the enforcement gate on the same trace can lag by up to 60 seconds. This asymmetry is deliberate: dashboards optimise for accuracy, the ingestion gate optimises for the documented p95 < 25 ms budget on POST /traces.

Endpoints

GET /api/v1/usage/current
GET /api/v1/usage/history

The legacy unversioned mount /api/usage returns the same payload with a Deprecation header; migrate to /api/v1/usage.

Get current usage

GET /api/v1/usage/current

Returns the live month-to-date counter for the caller's organisation plus a small bundle of dashboard-friendly derived fields.

curl
curl https://api.adjudon.com/api/v1/usage/current \
-H "Authorization: Bearer $ADJUDON_JWT"
Python
import os, requests
r = requests.get(
"https://api.adjudon.com/api/v1/usage/current",
headers={"Authorization": f"Bearer {os.environ['ADJUDON_JWT']}"},
)
u = r.json()["data"]
print(f"{u['thisMonth']:,} / {u['limit']:,} ({u['percentUsed']}%)")

Response fields (200 OK):

FieldTypeDescription
planenumsandbox, scale, governance, enterprise, custom
limitnumber | nullMonthly trace ceiling; null for custom
unlimitedbooleantrue only for custom
thisMonthnumberTrace count from the 1st of this month, server time (UTC)
percentUsednumber0-100+; 0 for unlimited plans
remainingnumber | nullmax(0, limit - thisMonth); null when unlimited
resetDatestring (ISO 8601)First of next month, server time
todaynumberTrace count from local midnight
dailyAveragenumberround(thisMonth / dayOfMonth)
projectedMonthlynumberround(dailyAverage * daysInMonth)
lastMonthnumberTrace count for the prior calendar month
monthOverMonthChangenumberPercent; 100 when lastMonth=0 and thisMonth>0; 0 when both zero
totalAllTimenumberLifetime trace count, no time filter
statusenumok < 90% ≤ warning < 100% ≤ exceeded

The status band is the same one the dashboard uses to colour the usage bar and the same one the warning notification fires on at 90% during ingestion. Errors: 401, 404 NOT_FOUND (org missing), 500 INTERNAL_ERROR.

Get usage history

GET /api/v1/usage/history

Returns the last twelve calendar months of trace counts — the exact array the dashboard's trend chart consumes. The current month is always the last element; partial-month counts are included as-is.

curl
curl https://api.adjudon.com/api/v1/usage/history \
-H "Authorization: Bearer $ADJUDON_JWT"

Response (200 OK):

{
"success": true,
"data": [
{ "label": "Jun 2025", "count": 41203 },
{ "label": "Jul 2025", "count": 58719 },
{ "label": "Aug 2025", "count": 72104 },
{ "label": "Sep 2025", "count": 91386 },
{ "label": "Oct 2025", "count": 115720 },
{ "label": "Nov 2025", "count": 99814 },
{ "label": "Dec 2025", "count": 61905 },
{ "label": "Jan 2026", "count": 88240 },
{ "label": "Feb 2026", "count": 94111 },
{ "label": "Mar 2026", "count": 102877 },
{ "label": "Apr 2026", "count": 110345 },
{ "label": "May 2026", "count": 47208 }
]
}

Labels are server-formatted in en-US ("Mon YYYY"); render-side locale conversion is the dashboard's responsibility. Errors: 401, 404, 500.

Common gotchas

  • No API-key path. This is a JWT-only resource; the SDK does not surface it because the SDK authenticates with adj_live_* / adj_agent_* keys. Read it from a session-authenticated dashboard or a server-to-server JWT exchange.
  • Server time is UTC. Month boundaries are new Date(y, m, 1) in the API server's local time, which is UTC on Fly.io Frankfurt. A trace ingested at 23:30 in Europe/Berlin on the last of the month may land in the next month on the meter; this matches the Stripe invoice cut-off.
  • No usage webhook today. Adjudon does not fire a usage.threshold event. Page on the 90% threshold by configuring an Alert on the org-level cpiScore or by polling /usage/current on a five-minute cron and reacting to the status field.
  • The unlimited plan still returns numbers. thisMonth, today, dailyAverage, projectedMonthly, lastMonth, totalAllTime are populated for every plan; only limit, remaining, and percentUsed change shape under unlimited.
  • Sandbox is the only hard block. scale, governance, enterprise keep ingesting after the limit and the overage flows through the Stripe metered-billing line item; the trace is never rejected. The status: 'exceeded' flag is informational, not a rate-limit trigger, on those plans.

See also

  • Billing API — the Stripe checkout, portal, and subscription state this counter feeds into
  • Plans & Features — the feature-gate matrix and the overage-rate explainer
  • Traces API — the source of truth this meter reads from
  • Alerts API — how to page on the 90% threshold without a usage webhook
  • Error Codes — the full error taxonomy