All endpoints are served over HTTPS. Base URL: https://tokage.dev
All requests must include an API key. Keys are prefixed tok_live_ for production environments. Use either header format:
# Bearer token (recommended)
curl https://tokage.dev/api/v1/events \
-H "Authorization: Bearer tok_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ ... }'
# X-API-Key header (alternative)
curl https://tokage.dev/api/v1/events \
-H "X-API-Key: tok_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ ... }'API keys are scoped to a tenant. All ingested events are automatically isolated by tenant at the database layer — you cannot read or write another tenant's data.
Ingest a single token usage event. Returns HTTP 202 Accepted only after the event has been durably written to Redpanda with acks=all.
Request
POST /api/v1/events
Content-Type: application/json
Authorization: Bearer tok_live_...
{
"schema_version": 1,
"event_id": "0190cfb2-1234-7000-8000-abcdef012345", // optional: UUIDv7
"model_provider": "anthropic",
"model_id": "claude-sonnet-4-6",
"input_tokens": 512,
"output_tokens": 128,
"total_tokens": 640,
"timestamp_client": "2026-02-24T00:00:00Z", // optional: RFC 3339
"application_id": "my-app", // optional
"user_id": "alice@example.com", // optional
"team_id": "engineering", // optional
"environment": "production", // optional
"metadata": { "session_id": "abc123" }, // optional: max 64 keys
"tags": ["gpt-4-upgrade", "experiment-a"] // optional: max 32 tags
}Response
HTTP/1.1 202 Accepted
Content-Type: application/json
{
"event_id": "0190cfb2-1234-7000-8000-abcdef012345"
}202 guarantee: A 202 response means the event is durable. It will not be lost. If you receive anything other than 202, the event was not committed.
Ingest up to 1,000 events in a single request. Maximum body size is 5 MB. Each event is individually validated; the entire batch is either accepted or rejected atomically.
Request
POST /api/v1/events/batch
Content-Type: application/json
Authorization: Bearer tok_live_...
{
"events": [
{
"schema_version": 1,
"model_provider": "openai",
"model_id": "gpt-4o",
"input_tokens": 1024,
"output_tokens": 256,
"total_tokens": 1280
},
{
"schema_version": 1,
"model_provider": "anthropic",
"model_id": "claude-haiku-4-5",
"input_tokens": 200,
"output_tokens": 80,
"total_tokens": 280
}
]
}Response
HTTP/1.1 202 Accepted
Content-Type: application/json
{
"accepted": 2,
"event_ids": [
"0190cfb2-aaaa-7000-8000-111111111111",
"0190cfb2-bbbb-7000-8000-222222222222"
]
}All analytics endpoints require from and to query parameters in RFC 3339 format. Results are scoped to your tenant automatically.
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/analytics/cost-by-model | Cost and token totals grouped by model |
| GET | /api/v1/analytics/cost-by-application | Cost and token totals grouped by application_id |
| GET | /api/v1/analytics/cost-by-user | Cost and token totals grouped by user_id |
| GET | /api/v1/analytics/cost-by-team | Cost and token totals grouped by team_id |
| GET | /api/v1/analytics/daily-summary | Daily cost and token totals for a date range |
| GET | /api/v1/analytics/hourly-usage | Hourly cost and token totals for a date range |
| POST | /api/v1/analytics/query | Ad-hoc raw query (SQL-like filter expression) |
Example: cost-by-model
GET /api/v1/analytics/cost-by-model?from=2026-02-01T00:00:00Z&to=2026-02-24T00:00:00Z
Authorization: Bearer tok_live_...
HTTP/1.1 200 OK
{
"data": [
{ "model_provider": "anthropic", "model_id": "claude-sonnet-4-6", "total_cost_usd": 12.45, "total_tokens": 1250000, "event_count": 842 },
{ "model_provider": "openai", "model_id": "gpt-4o", "total_cost_usd": 8.32, "total_tokens": 830000, "event_count": 521 }
],
"total": 2
}Example: daily-summary
GET /api/v1/analytics/daily-summary?from=2026-02-01T00:00:00Z&to=2026-02-07T00:00:00Z
Authorization: Bearer tok_live_...
HTTP/1.1 200 OK
{
"data": [
{ "date": "2026-02-01", "total_cost_usd": 3.21, "input_tokens": 450000, "output_tokens": 120000, "event_count": 201 },
{ "date": "2026-02-02", "total_cost_usd": 2.87, "input_tokens": 380000, "output_tokens": 105000, "event_count": 178 }
],
"total": 7
}Do not include input_cost_usd, output_cost_usd, or total_cost_usd. Cost is stamped server-side by the Price Registry. Submitting non-zero cost values causes a 400 rejection.
| Field | Type | Required | Notes |
|---|---|---|---|
| schema_version | integer | required | Always 1 |
| event_id | string (UUIDv7) | optional | Auto-generated if omitted. Used for deduplication. |
| model_provider | string | required | "anthropic", "openai", "google", etc. |
| model_id | string | required | "claude-sonnet-4-6", "gpt-4o", etc. |
| input_tokens | integer | required | Prompt tokens consumed |
| output_tokens | integer | required | Completion tokens generated |
| total_tokens | integer | required | Authoritative billing total (server rejects cost fields) |
| timestamp_client | string (RFC 3339) | optional | Client-side event time. Defaults to server receipt time. |
| application_id | string | optional | Logical application name for attribution |
| user_id | string | optional | End-user identifier. Hashed before storage. |
| team_id | string | optional | Team or cost-centre identifier |
| environment | string | optional | "production", "staging", "development" |
| metadata | object | optional | Arbitrary key/value dimensions. Max 64 keys, string values only. |
| tags | object | optional | Key-value labels. Max 32 entries, string keys and values. |
SDKs automatically retry on 5xx errors with exponential backoff (3 attempts). Do not retry 4xx errors — they indicate a payload or auth problem.
| Code | Title | Description |
|---|---|---|
202 | Accepted | Event durably written to Redpanda. Safe to discard your copy. |
400 | Bad Request | Malformed JSON or invalid field values. Fix the payload; do not retry. |
401 | Unauthorized | Missing or invalid API key. Check the Authorization or X-API-Key header. |
403 | Forbidden | API key is valid but lacks permission for this operation. |
413 | Payload Too Large | Batch body exceeds 5 MB. Split into smaller batches. |
422 | Unprocessable Entity | Schema validation failed. Check the "details" array in the response. |
429 | Too Many Requests | Rate limit exceeded. Check the Retry-After header (seconds). |
5xx | Server Error | Transient server error. SDKs retry automatically (3 attempts, exponential backoff). |
Example error response
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"error": "validation failed",
"details": [
{ "field": "total_tokens", "message": "must be >= input_tokens + output_tokens" }
]
}