Formats & event schema¶
A trace is the record Axium emits for every request that reaches a proxy app runtime: successful, failed, denied by a policy or served from cache. This page is the contract for anyone consuming traces, whether from the PostgreSQL table or from an HTTP batch.
| Schema version | 1 (header x-axium-trace-version on HTTP; telemetry.schema_version in PostgreSQL) |
| Cardinality | Exactly one trace per request |
| Fields emitted | 42, always present, always in the order below |
| Computed in PostgreSQL only | latency_ms, tokens_total, cost |
| Unknown values | null. Token counts are never defaulted to 0. |
| Timestamps | ISO-8601 in UTC with milliseconds (2026-10-19T10:15:30.120Z) |
Fields¶
Identity¶
| Field | Type | Description |
|---|---|---|
trace_id |
uuid | Minted when the request enters Axium and returned to the client in the x-axium-trace-id response header. Lets a client report "this request" and you find it. |
span_id |
uuid | One span per trace in this version. |
Timing¶
| Field | Type | Description |
|---|---|---|
started_at |
timestamp | When the request entered Axium. |
ended_at |
timestamp | When the response finished. For streams, when the last chunk was sent. |
latency_ms |
integer · computed | ended_at - started_at. |
Origin¶
| Field | Type | Description |
|---|---|---|
source_module |
proxy |
Reserved for other modules in future versions. |
proxy_app_id |
uuid · null | The proxy app in the URL. null only when the app does not exist. |
api_key_id |
uuid · null | The key that authenticated. |
client_ip |
string · null | req.ip. The real client only with TRUST_PROXY=true behind a proxy. Personal data. |
client_country |
null | Reserved. No geolocation in this version. |
client_user_id |
string · null | Value of the x-axium-user-id request header, if the client sent one. |
Provider and model¶
| Field | Type | Description |
|---|---|---|
provider_id |
uuid · null | |
provider_type |
openai · azure-openai · google-aistudio · google-enterprise · null |
|
provider_region |
string · null | Provider's configured region, global by default. |
model_id |
uuid · null | Axium's model record. |
model_used |
string · null | Deployed model name sent upstream. On Vertex includes the publisher: google/gemini-2.5-flash, anthropic/claude-…. |
model_requested |
string · null | What the client asked for. null when the app's default model was used. |
dialect |
openai · gemini |
Wire protocol of the gateway. |
surface |
see below | Which endpoint family was called. |
upstream_path |
string · null | Path sent to the provider, e.g. /v1/chat/completions. null if the request never left Axium (denied, cache hit). |
surface values: openai.chat, openai.responses, openai.models,
openai.files, gemini.generate, gemini.interactions, gemini.models,
gemini.files, vertex.generate.
Tokens¶
All integer · null. null means the provider did not report it.
| Field | Source |
|---|---|
tokens_in |
Prompt tokens. |
tokens_out |
Completion tokens. |
tokens_cache_read |
Prompt tokens served from the provider's cache (cached_tokens). |
tokens_cache_write |
Reserved, always null. |
tokens_reasoning |
Reasoning tokens (reasoning_tokens). |
tokens_saved |
Reserved, always null. |
tokens_total |
computed: tokens_in + tokens_out. |
Cost¶
All number · null, six decimals, in the currency of the prices configured on
the model. null when the model has no price.
| Field | Definition |
|---|---|
cost_in |
tokens_in × cost_input_1m / 1e6. Cached tokens are billed at full input price. |
cost_out |
tokens_out × cost_output_1m / 1e6. |
cost_saved |
Reserved, always null. |
cost |
computed: cost_in + cost_out. |
Request shape¶
| Field | Type | Description |
|---|---|---|
stream |
boolean | Whether the client asked for streaming. |
max_tokens |
integer · null | max_completion_tokens or max_tokens (chat), max_output_tokens (responses), generationConfig.maxOutputTokens (Gemini/Vertex). |
Result¶
| Field | Type | Description |
|---|---|---|
status |
success · error · denied · timeout · cancelled |
denied = Axium refused before calling the provider. cancelled = the client closed a stream early. |
http_status |
integer | What the client received. |
error_origin |
axium · provider · null |
Who produced the error. |
error_category |
see below · null | Normalised category. |
error_message |
string · null | The provider's error.message, or Axium's own message. |
cache_hit |
boolean · null | true/false only when the cache policy ran; null otherwise. |
fallback_used |
null | Reserved. |
error_category values: auth_error, permission_denied, not_found,
rate_limit, quota_exceeded, content_filter, invalid_request,
server_error, timeout, connection_error, unknown. For non-streamed
provider errors it is derived from the status: 401 → auth_error, 403 →
permission_denied, 404 → not_found, 429 → rate_limit, 5xx →
server_error, other 4xx → invalid_request.
Policies¶
| Field | Type | Description |
|---|---|---|
policies_applied |
string[] | Policies that ran, whether or not they changed anything: firewall.inbound, dlp_prompt, cache. |
denial_reason |
string · null | Only with status = denied. See the table below. |
denial_reason |
HTTP | Meaning |
|---|---|---|
app_not_found |
404 | No proxy app with that id. |
app_not_ready |
403 | App is draft or inactive. |
budget_exhausted |
429 | The app's budget for the current period is spent. |
model_ambiguous |
400 | The name matches more than one allowed model. |
model_not_allowed |
403 | Model is not in the app's allow-list. |
no_default_model |
400 | Client sent no model and the app has no default. |
default_model_unusable |
403 | The default model is inactive or not allowed. |
provider_inactive |
403 | The model's provider is deactivated. |
provider_type_mismatch |
400 | E.g. an OpenAI model through the Gemini gateway. |
dialect_mismatch |
400 | The model's provider does not speak this gateway's protocol. |
streaming_unsupported |
400 | Streaming requested on a surface that does not support it. |
firewall_denied |
403 | Client IP blocked by firewall.inbound. |
dlp_blocked |
403 | dlp_prompt found data the policy blocks. |
policy_config_invalid |
500 | The app's policy document could not be applied. |
Context¶
| Field | Type | Description |
|---|---|---|
meta_tag |
string · null | Value of the x-axium-meta-tag request header. |
tags |
object (string → string) | Every other x-axium-<name> request header, keyed by <name>. Repeated headers are dropped. |
attributes |
object | Structured findings from policies. Today only dlp_prompt. |
attributes.dlp_prompt:
{
"action": "none | keep | mask | block | error",
"entities": [ { "entity": "IBAN", "kind": "financial", "country": "ES", "confirmed_by": "checksum" } ],
"error": "optional message when action = error"
}
Entity names and kinds only. The matched values are never recorded.
Sending context from your application¶
Three request headers feed the trace and are never forwarded to the provider:
x-axium-user-id: user-42 → client_user_id
x-axium-meta-tag: release-2026.10 → meta_tag
x-axium-team: sales → tags.team
x-axium-feature: summarise → tags.feature
Most SDKs accept default headers:
new OpenAI({ apiKey, baseURL, defaultHeaders: { 'x-axium-user-id': user.id, 'x-axium-team': 'sales' } });
Examples¶
Successful chat completion (envelope)
{
"version": 1,
"data": [
{
"trace_id": "3f0d7a0e-8a56-4f4a-9b7b-6d1a7c2b9d10",
"span_id": "c2a1f0b4-1c3e-4a9d-8e1f-0a2b3c4d5e6f",
"started_at": "2026-10-19T10:15:30.120Z",
"ended_at": "2026-10-19T10:15:31.480Z",
"source_module": "proxy",
"proxy_app_id": "9a1b2c3d-4e5f-4a6b-8c7d-0e1f2a3b4c5d",
"api_key_id": "7e6d5c4b-3a2f-4e1d-9c8b-7a6f5e4d3c2b",
"client_ip": "203.0.113.10",
"client_country": null,
"client_user_id": "user-42",
"provider_id": "11111111-2222-4333-8444-555555555555",
"provider_type": "openai",
"provider_region": "global",
"model_id": "22222222-3333-4444-8555-666666666666",
"model_used": "gpt-4o-mini",
"model_requested": "gpt-4o-mini",
"dialect": "openai",
"surface": "openai.chat",
"upstream_path": "/v1/chat/completions",
"tokens_in": 120,
"tokens_out": 35,
"tokens_cache_read": 0,
"tokens_cache_write": null,
"tokens_reasoning": 0,
"tokens_saved": null,
"cost_in": 0.000018,
"cost_out": 0.000021,
"cost_saved": null,
"stream": false,
"max_tokens": 256,
"error_origin": null,
"status": "success",
"http_status": 200,
"error_category": null,
"error_message": null,
"cache_hit": false,
"fallback_used": null,
"policies_applied": ["firewall.inbound", "dlp_prompt", "cache"],
"denial_reason": null,
"meta_tag": "release-42",
"tags": { "department": "sales" },
"attributes": { "dlp_prompt": { "action": "none", "entities": [] } }
}
]
}
Denied by the model allow-list (one NDJSON line)
{"trace_id":"5c1e…","span_id":"a9f3…","started_at":"2026-10-19T10:20:00.000Z","ended_at":"2026-10-19T10:20:00.004Z","source_module":"proxy","proxy_app_id":"9a1b…","api_key_id":"7e6d…","client_ip":"203.0.113.10","client_country":null,"client_user_id":null,"provider_id":null,"provider_type":null,"provider_region":null,"model_id":null,"model_used":null,"model_requested":"gpt-5","dialect":"openai","surface":"openai.chat","upstream_path":null,"tokens_in":null,"tokens_out":null,"tokens_cache_read":null,"tokens_cache_write":null,"tokens_reasoning":null,"tokens_saved":null,"cost_in":null,"cost_out":null,"cost_saved":null,"stream":false,"max_tokens":null,"error_origin":"axium","status":"denied","http_status":403,"error_category":"permission_denied","error_message":"Model \"gpt-5\" is not allowed for this proxy app","cache_hit":null,"fallback_used":null,"policies_applied":["firewall.inbound"],"denial_reason":"model_not_allowed","meta_tag":null,"tags":{},"attributes":{}}
Cache hit
status = success, http_status = 200, cache_hit = true, all token and
cost fields null, upstream_path = null. The client also receives the
x-axium-cache: hit response header.
Stream cancelled by the client
status = cancelled, stream = true, ended_at at the moment the
connection dropped, token fields filled with whatever the provider had
reported before the cut, usually null.
Versioning¶
The schema version increments when a field changes meaning or is removed.
Adding a field does not bump it. Consumers should ignore unknown fields and
check x-axium-trace-version (HTTP) or telemetry.schema_version
(PostgreSQL).