CortexDB Docs
Docs for CortexDB 0.12.8, the version running on CortexDB Cloud (api.cortexdb.ai).
Operations

Token & Cost Ledger

GET /v1/admin/usage — durable per-role token and dollar accounting, what it measures exactly, what it only bounds, and what it refuses to price.

Every LLM and embedding call the server makes is recorded in a durable usage ledger at <data_dir>/usage/ledger.json, one bucket per UTC day, pipeline role, tenant and model. The same data is on the Admin Console's Observability page ("Tokens & cost") and as a month-to-date strip on Overview.

The route

GET /v1/admin/usage?from=2026-09-01&to=2026-09-30          # inclusive days, YYYY-MM-DD
GET /v1/admin/usage?period=mtd                              # month to date; or period=all (since inception). Used only when from/to are absent
GET /v1/admin/usage?role=answer&tenant=org:acme
GET /v1/admin/usage?format=csv                              # flat per-bucket export for a billing pipeline

An unknown period or role is a 400 (INVALID_PERIOD / INVALID_ROLE). Requires operator access: the ledger is process-global and spans every tenant on the node.

{
  "range": { "from": null, "to": null, "observed_from": null },
  "durable": true,
  "total":         { "calls", "failed_calls", "cached_calls", "incomplete_calls", "unpriced_calls",
                     "tokens_input", "tokens_output", "tokens_total", "tokens_unsplit",
                     "cost_usd", "latency_ms_total", "latency_ms_mean" },
  "by_day":        [ { "day": "2026-09-18", ...same counters } ],
  "by_role":       [ { "role": "embedding", ... } ],
  "by_role_model": [ { "role": "embedding", "model": "text-embedding-3-small", ... } ],
  "by_tenant":     [ { "tenant": "org:acme", ... } ],
  "cost_basis":    { "tokens": "measured", "cost_usd": "router_reported", "recompute_from": "by_role_model", "note": "..." },
  "instrumentation": { "by_role": [ { "role", "instrumented", "coverage": "full" | "partial" | "none", "covers" } ] },
  "enrichment_coverage": { "events_enriched", "events_with_facts", "facts_total", "facts_per_event", "events_failed", ... },
  "enrichment_backlog":  { "jobs_pending", "jobs_queued_total", "jobs_completed_total" }
}

The CSV columns are day, role, tenant, model, calls, failed_calls, cached_calls, incomplete_calls, unpriced_calls, tokens_input, tokens_output, tokens_unsplit, cost_usd, latency_ms. A tenant that cannot be attributed is written as __unattributed__.

Roles: embedding, entity_extraction, enrichment, enrichment_qa, recall, answer, verifier, reranker, plus the background lanes (layer_build and the maintenance tick). entity_extraction and recall are two uses of the one CORTEX_LLM_* router.

Read the tokens, then the dollars

by_role_model carries exact token counts beside the model name. That is the durable record, and the one to re-price from: multiply the tokens (or the CSV export) by a corrected rate, or by your own rate if you bring your own model, without re-running anything. cost_usd is priced from the server's price book when each call is recorded; changing the rates later does not re-price calls already recorded.

Set CORTEX_MODEL_PRICES to give a model a rate, in USD per million tokens, input/output, comma-separated:

CORTEX_MODEL_PRICES=gpt-5.6-terra=2/12,gpt-5.6-luna=0.2/1.2,gpt-5.6-sol=4/20,gpt-6-astra=10/50

It takes precedence over per-provider cost_in/cost_out in CORTEX_LLM_PROVIDERS, which take precedence over the built-in table. The built-in table covers the Claude families, gpt-4o, gpt-4o-mini, gpt-4.1 (and -mini, -nano), gpt-5 (and -mini, -nano) and the text-embedding-3-* / ada-002 embedding models.

Set prices for GPT-5.6 and GPT-6 models

The built-in table has no gpt-5.6-* or gpt-6-* entries; those models are priced at the gpt-5 rate ($1.25 / $10 per million) unless you give them a rate. Set CORTEX_MODEL_PRICES for every GPT-5.6 and GPT-6 model you run, as above. See Cost Planning.

The ledger shows the model actually called, not diagnostics.answer_model

The answer lane always calls the configured CORTEX_ANSWER_MODEL; a per-request answer_model is echoed in diagnostics.answer_model only. The ledger records the configured model, which is the one you are billed for. To answer with another model, change CORTEX_ANSWER_MODEL.

Three marks so the report cannot flatter itself

FieldMeaning
unpriced_callsThis build has no rate for that model. The tokens are exact; the dollars are zero because unmeasured, not because free. The UI shows a hatched "unpriced" chip, never $0.00.
tokens_unsplitThe provider reported one combined number instead of an input/output split (the entity-extraction lane does this). Kept separate on purpose: pricing it at an output rate would overstate that lane several-fold.
incomplete_callsWhile non-zero, the token fields are a floor ("≥ n" in amber in the UI).

And two roles are declared uninstrumented rather than read as zero: instrumentation.by_role reports coverage: full | partial | none per role. The reranker records nothing (its interface carries no token or cost data and its provider bills per search unit), and four small fact-join calls inside the answer path discard their usage before it can be recorded, so the answer lane is partial. A window with no answer calls therefore reads "this lane was idle", not "no dollars will ever be recorded". Lanes pointed at an operator-controlled endpoint (CORTEX_RERANKER_URL, a local model) report 0.0 cost by design.

Streamed answers are booked in full

A streamed /v1/answer ("stream": true) books its input tokens as well as its output tokens.

Enrichment coverage

The same payload carries enrichment_coverage (events enriched, events with facts, facts per event, failed) and enrichment_backlog (a live gauge of pending jobs), so "is enrichment keeping up?" and "what does it cost?" are one call. See Self-hosting defaults for what enrichment is.

On this page