API Reference

Base URL: http://localhost:8080

Authentication

When API keys are configured, all /v1/* endpoints require a Bearer token:

Authorization: Bearer your-api-key-here

When no keys are configured (default), authentication is disabled. /health and /metrics are always public.

POST /v1/events

Ingest a single event. Waits for durable storage. Times out after 10 seconds. Supports Idempotency-Key header to prevent duplicate creation on retries.

Request body

FieldTypeRequiredDescription
modelstringyesModel name (e.g. "gpt-4o")
providerstringyesProvider key (e.g. "openai")
usage.input_tokensu32noInput tokens
usage.output_tokensu32noOutput tokens
usage.cache_read_input_tokensu32noCached prompt tokens
usage.reasoning_tokensu32noReasoning/thinking tokens
latency.ttft_msu32noTime to first token (ms)
latency.total_msu32noEnd-to-end latency (ms)
timestampstring | i64noISO 8601 or epoch nanos
user_idstringnoUser identifier
api_key_idstringnoAPI key for attribution
org_idstringnoOrganization for rollups
sourcestringnoSending system
http_statusu16noUpstream status code
flags.streamingboolnoStreamed response
flags.tool_callsboolnoTool calls present
request_bodyobjectnoFull request JSON (compressed)
response_bodyobjectnoFull response JSON (compressed)
error.kindstringnorate_limited, auth_failed, etc.

Response 201 Created

{
  "id": "01J5XQKR4M2E3N8V7P6Y1WDCBA",
  "cost_nanodollars": 6250000,
  "model": "gpt-4o",
  "provider": "openai"
}

POST /v1/events/batch

Ingest up to 10,000 events. Fire-and-forget by default. Set X-Keplor-Durable: true header to await flush confirmation for each event.

Request

{"events": [{"model": "gpt-4o", "provider": "openai", ...}, ...]}

Response 201 or 207 Multi-Status

{
  "results": [
    {"id": "01J5X...", "cost_nanodollars": 100, ...},
    {"error": "validation: model must not be empty"}
  ],
  "accepted": 1,
  "rejected": 1
}

GET /v1/events

Query events with filtering and cursor-based pagination.

Query parameters

ParamTypeDescription
user_idstringFilter by user
modelstringFilter by model
providerstringFilter by provider
sourcestringFilter by source
fromi64After this epoch ns
toi64Before this epoch ns
limitu32Max results (default 50, max 1000)
cursori64Pagination cursor

Response 200 OK

{
  "events": [{
    "id": "01J5XQKR...",
    "timestamp": 1700000000000000000,
    "model": "gpt-4o",
    "provider": "openai",
    "usage": {"input_tokens": 500, "output_tokens": 200, ...},
    "cost_nanodollars": 6250000,
    "latency_total_ms": 1200,
    "streaming": false
  }],
  "cursor": 1700000000000000000,
  "has_more": false
}

GET /v1/quota

Real-time cost and event count from the event table. At least one of user_id or api_key_id is required.

Query parameters

ParamTypeRequiredDescription
user_idstringno*Filter by user
api_key_idstringno*Filter by API key
fromi64yesEvents on or after this epoch ns

* At least one of user_id or api_key_id must be provided.

Response 200 OK

{
  "cost_nanodollars": 48250000,
  "event_count": 137
}

GET /v1/rollups

Pre-aggregated daily rollup rows broken down by provider and model. The background rollup task refreshes every 60 seconds.

Query parameters

ParamTypeRequiredDescription
user_idstringnoFilter by user
api_key_idstringnoFilter by API key
fromi64yesStart epoch ns (converted to day boundary)
toi64yesEnd epoch ns (converted to day boundary)
limitu32noMax rows (default 100, max 1000)
offsetu32noOffset for pagination (default 0)

Response 200 OK

{
  "rollups": [{
    "day": 1700006400,
    "user_id": "user-123",
    "api_key_id": "key-abc",
    "provider": "openai",
    "model": "gpt-4o",
    "event_count": 42,
    "error_count": 1,
    "input_tokens": 21000,
    "output_tokens": 8400,
    "cache_read_input_tokens": 5000,
    "cache_creation_input_tokens": 0,
    "cost_nanodollars": 18750000
  }],
  "has_more": false
}

GET /v1/stats

Aggregated period statistics summed from daily rollups. Optionally group by model for per-model cost breakdowns.

Query parameters

ParamTypeRequiredDescription
user_idstringnoFilter by user
api_key_idstringnoFilter by API key
fromi64yesStart epoch ns
toi64yesEnd epoch ns
providerstringnoFilter by provider
group_bystringnoSet to "model" to group by provider + model
limitu32noMax rows (default 100, max 1000)
offsetu32noOffset for pagination (default 0)

Response 200 OK

{
  "stats": [{
    "provider": "openai",
    "model": "gpt-4o",
    "event_count": 137,
    "error_count": 3,
    "input_tokens": 68500,
    "output_tokens": 27400,
    "cache_read_input_tokens": 12000,
    "cache_creation_input_tokens": 0,
    "cost_nanodollars": 48250000
  }],
  "has_more": false
}

DELETE /v1/events/:id

Delete a single event by ID.

Returns 204 No Content if deleted, 404 Not Found if the event does not exist.

DELETE /v1/events?older_than_days=N

Bulk delete events older than N days. Equivalent to keplor gc via HTTP. older_than_days must be greater than 0.

Response 200 OK

{
  "events_deleted": 1234,
  "blobs_deleted": 56
}

GET /v1/events/export

Stream all matching events as JSON Lines (application/x-ndjson). Accepts the same filter parameters as GET /v1/events but with no result-set limit. Each line is one JSON event object.

GET /health

Liveness probe. Returns 200 when healthy, 503 when degraded.

{
  "status": "ok",
  "version": "0.1.0",
  "db": "connected",
  "queue_depth": 0,
  "queue_capacity": 32768,
  "queue_utilization_pct": 0
}

GET /metrics

Prometheus text exposition format.

# TYPE keplor_events_ingested_total counter
keplor_events_ingested_total{provider="openai"} 42

Request/Response headers

HeaderDirectionDescription
AuthorizationRequestBearer <secret> (required when keys configured)
Idempotency-KeyRequestOptional. Prevents duplicate event creation on retries. Cached for 5 min (configurable).
X-Keplor-DurableRequestSet to true on batch endpoint to await flush confirmation. Default: fire-and-forget.
X-Request-IdBothEchoed if sent; otherwise Keplor generates a ULID and returns it.
Retry-AfterResponseSeconds until rate limit resets (returned with 429).

Error responses

{"error": "validation: model must not be empty"}
StatusWhenRetry?
204Event deleted successfullyNo
400Validation error, bad JSON, invalid timestampNo
401Missing or invalid API keyNo
404Event not found (DELETE)No
408Request exceeded request_timeout_secsYes
422Unknown providerNo
429Per-key rate limit exceededYes (after Retry-After)
500Storage failure, write timeoutYes (with backoff)
503Batch writer overloaded (back-pressure)Yes (with backoff)
507Database size limit exceededYes (run GC or increase limit)