Metering API reference
Base URL: https://r3ckon.com
The metering API accepts usage metadata and nothing else. It exists so gateways, self-hosted stacks, and internal pipelines can feed the ledger without a managed provider connection.
Authentication
Every request carries a bearer key:
Authorization: Bearer aiok_ingest_…
Keys are account-scoped and role-scoped:
aiok_admin_…: full console access plus every API below. This is the key revealed once at signup.aiok_ingest_…: may push usage (both ingest endpoints) but cannot read the console or manage the account.aiok_sdk_…: may push to the SDK feed only.aiok_read_…: read-only. May call the account read surfaces (the overview API, certificate PDFs, and every MCP tool) and can neither push usage nor manage anything. The only key that belongs in an MCP client config.
Only a SHA-256 hash of each key is stored. Rotation is currently by request (info@r3ckon.com) after billing-email verification.
POST /api/v1/ingest/authoritative
The billing-grade feed: daily (or coarser) aggregates that estimates, retirements, and invoices are computed from.
curl -X POST https://r3ckon.com/api/v1/ingest/authoritative \
-H "Authorization: Bearer $R3CKON_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": [{
"apiConnection": "anthropic",
"model": "claude-sonnet-5",
"region": "us-east-1",
"inputTokens": 18400000,
"cachedInputTokens": 12100000,
"outputTokens": 2300000,
"reasoningTokens": 410000,
"serviceTier": "standard",
"timestamp": "2026-08-04T00:00:00Z"
}]
}'
Response: { "accepted": 1 }
Event fields
| Field | Type | Required | Notes |
|---|---|---|---|
model |
string (1..200) | yes | The provider's model identifier, verbatim. Unknown models estimate under the registry's most conservative class. |
inputTokens |
integer >= 0 | yes | The FULL input stream, including any cached portion. |
outputTokens |
integer >= 0 | yes | Completion tokens. |
region |
string (1..64) | yes | Cloud region code when known (e.g. us-east-1). Unrecognized values estimate under conservative unknown-facility parameters. A recognized code selects that cloud's published facility profile, which can lower the estimate relative to the unknown default, so report the region truthfully; region values are recorded with the event and are auditable. |
timestamp |
ISO 8601 datetime with offset | yes | The period the aggregate covers (use the day start for daily buckets). |
cachedInputTokens |
integer >= 0 | no | The cached SUBSET of inputTokens. Omitted means unreported, not zero. |
reasoningTokens |
integer >= 0 | no | The reasoning SUBSET of outputTokens, where the provider reports it (never billed twice). Omitted means unreported; for reasoning-class models that triggers the methodology's conservative hidden-reasoning multiplier. |
serviceTier |
standard | batch | priority |
no | Batch and priority tiers carry their own energy multipliers. |
apiConnection |
string (1..64) | no | Which source produced this usage; groups the console's per-API filters. Accounts support up to 50 distinct connections. |
endUserPseudoId |
string (<=128) | no | An opaque pseudonymous id if you segment end users. Never send anything identifying. |
The content boundary (HTTP 422)
Every payload is deep-scanned before parsing. If any key anywhere in the
JSON matches a content-shaped name (prompt, completion, content,
messages, message, text, input, output, system, body,
response, query, answer, transcript), the request is rejected with
422 and nothing is stored:
{ "error": "Ingest rejected: payload contains content-shaped key \"prompt\". This platform is metadata-only and must never receive prompt or completion content." }
This is an architectural invariant, enforced by tests, not a policy.
Idempotency and re-submission
By default a POST ADDS the events you send. To restate a period instead,
include a replace object naming the window you own:
{
"replace": { "apiConnection": "anthropic-prod", "start": "2026-08-05", "end": "2026-08-08" },
"events": [ … ]
}
Everything previously recorded for that apiConnection in that window
(start inclusive, end exclusive) is removed, then your events are stored.
This makes an overlapping or repeated cron safe: re-running the same
window restates it rather than doubling it, and a missed day is fixed by
running again with a wider window.
The replace is scoped to the one apiConnection you name, so it can never
remove usage that arrived from a different source. Send replace on the
first request of a multi-batch upload only; later batches would otherwise
delete the rows the earlier ones just wrote.
The sync scripts do this for you.
Errors
| Status | Meaning |
|---|---|
400 |
Malformed JSON, missing events array, or field validation failure (message says which). |
401 |
Missing or unknown bearer key, or a role without ingest rights. |
422 |
Content boundary violation (see above). |
POST /api/v1/usage-events
The SDK feed: identical body and validation, accepted from sdk,
ingest, or admin keys. It powers live views and is never billed
from; billing and retirement always come from the authoritative feed.
Account APIs
The console is built on account-scoped APIs authenticated with your
aiok_admin_… key. The stable, supported surface today:
GET /api/admin/overview: everything the dashboard shows, as JSON: monthly footprint with P05/P50/P90/P95 per category, retirements, credit position, invoices, certificates, connections, filters.GET /api/admin/connections/POST(create) /DELETE /api/admin/connections/{id}/POST /api/admin/connections/{id}/syncwith{ "periodStart": "YYYY-MM-DD", "periodEnd": "YYYY-MM-DD" }.GET /api/admin/certificates/{id}/pdf: the certificate PDF.GET /api/admin/exports/{name}: reporting CSVs (water restoration summary, CDP Water and ESRS E3 inputs).POST /api/admin/billing-portal: a Stripe customer portal session URL.
These return JSON errors in the same { "error": "…" } shape. Endpoints
not listed here (reconcile, seeding) are operator-only and may change.