Dashboard Get started

List events

GET/api/v1/events

Returns a paginated list of captured API calls.

Query parameters

Parameter Type Description
session_id string Filter by session ID
provider string Filter by provider: anthropic, openai, xai, google, aws_bedrock, azure_openai, cohere, mistral
model string Filter by model name
team_id string Filter by team identifier (passed at ingest)
feature string Filter by feature label (passed at ingest)
since ISO 8601 Start of time range
until ISO 8601 End of time range
limit integer Max results (default 100, max 1000)
offset integer Pagination offset
include_payload boolean Include raw request/response body. Default false for PII safety

Response

{
  "events": [
    {
      "id": "evt_01abc…",
      "provider": "anthropic",
      "model": "claude-opus-4-7",
      "session_id": "my-session",
      "team_id": "platform-ai",
      "feature": "rag-rerank",
      "is_batch": false,
      "batch_id": null,
      "input_tokens": 1024,
      "output_tokens": 256,
      "cache_read": 800,
      "cache_creation": 224,
      "reasoning_tok": 0,
      "cached_tok": 0,
      "cost_usd": 0.0043,
      "duration_ms": 1240,
      "stop_reason": "end_turn",
      "payload": null,
      "timestamp": "2026-05-04T08:00:00Z"
    }
  ],
  "count": 1,
  "offset": 0
}

Batch API pricing

When an event has is_batch=true (set automatically by the SDK wrappers when they detect an Anthropic Message Batches or OpenAI Batch API response), cost_usd is computed at 50% of the standard per-token rate. The batch_id field carries the upstream batch identifier so you can correlate events that belong to the same batch.

Team and feature attribution

team_id and feature are optional ingest-time labels for sub-organization cost attribution. Pass them as kwargs on the SDK wrapper:

client = JournaledAnthropic(
    api_key="sk-ant-…",
    team_id="platform-ai",
    feature="rag-rerank",
)

Both columns are indexed and queryable on GET /api/v1/events/?team_id=…&feature=… and on the dashboard breakdown endpoints (?team_id=… / ?feature=…).

Payload visibility

payload is excluded from responses by default. Pass ?include_payload=true to receive the raw JSONB body. See PII controls for the org-level toggle that drops payloads at ingest entirely.


Trace ingest

POST/api/v1/ingest/trace

Generic OpenTelemetry-style trace ingest for callers who can't use the SDK wrappers — for example, a service already emitting OTLP/JSON via the OpenTelemetry collector. The body is the standard OTLP resourceSpans shape; TokenID reads the gen_ai.* semantic-convention attributes off each span and writes one otel_span event row per span.

curl -X POST https://token.audit.id/api/v1/ingest/trace \
  -H "Authorization: Bearer td_live_xxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "resourceSpans": [{
      "scopeSpans": [{
        "spans": [{
          "name": "anthropic.messages.create",
          "startTimeUnixNano": "1748044800000000000",
          "endTimeUnixNano":   "1748044801240000000",
          "attributes": [
            {"key": "gen_ai.system",                "value": {"stringValue": "anthropic"}},
            {"key": "gen_ai.request.model",         "value": {"stringValue": "claude-opus-4-7"}},
            {"key": "gen_ai.usage.input_tokens",    "value": {"intValue": "1024"}},
            {"key": "gen_ai.usage.output_tokens",   "value": {"intValue": "256"}},
            {"key": "gen_ai.response.cost_usd",     "value": {"doubleValue": 0.0043}},
            {"key": "gen_ai.response.id",           "value": {"stringValue": "req_011abc"}}
          ]
        }]
      }]
    }]
  }'

Returns 202 Accepted with {"accepted": N, "organization_id": "…"}. Workspace-scoped keys must carry the ingest permission.


Langfuse ingest

POST/api/v1/ingest/langfuse

Compatibility shim for teams already instrumented for Langfuse. Accepts a Langfuse observation-create webhook payload and writes one langfuse_generation event row per GENERATION-type observation. Other observation types are accepted but ignored (accepted: 0).

curl -X POST https://token.audit.id/api/v1/ingest/langfuse \
  -H "Authorization: Bearer td_live_xxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "observation-create",
    "body": {
      "id": "obs_abc",
      "type": "GENERATION",
      "model": "claude-opus-4-7",
      "startTime": "2026-05-21T10:30:00Z",
      "latency": 1240,
      "usage": {"input": 1024, "output": 256},
      "metadata": {"feature": "rag-rerank"}
    }
  }'

Point your Langfuse instance's outbound webhook at this URL with the org API key as a Bearer token. The X-Langfuse-Signature header is accepted but not verified — auth is on the bearer token.


Ingest behavior

Two cross-cutting behaviors apply to every ingest path, including the SDK wrappers and the two endpoints above.

Deduplication

A unique constraint on (organization_id, provider, model, api_key_hash, timestamp) rejects replays. The api_key_hash is a 16-char SHA-256 derived from the ingest API key id and the event's request_id (or session_id + model + timestamp as a fallback). On conflict, the duplicate row is silently skipped via ON CONFLICT DO NOTHING. SDK retries on transient failures are therefore safe — you cannot double-bill yourself by retrying.

NUL-byte stripping

PostgreSQL rejects \x00 in UTF-8 text columns, so the ingest path strips every character outside [\t\n\r\x20-\x7E] from every string field — and recursively from every nested string value in payload, tool_input, tool_output, and the _tokenid block. Numeric fields (token counts, cost, duration) are untouched. The stripping is invisible in normal use; if you're searching for events containing control characters or non-ASCII content, expect it to have been removed at the boundary.