Request IDs and metadata
Read the X-Request-Id header and each call's request_id, the Cruxy-Version header and the cruxy block, and quote a request when you report a problem.
Every response the gateway sends carries an X-Request-Id header, and a call
that succeeds also returns its own per-call request_id in the body. On an
error, the two are the same value. On a success they differ, and the body's id
is the one request logs and feedback take. Every response also names the API
version that served it, in the Cruxy-Version header.
# -i prints the response headers above the body. X-Request-Id and
# Cruxy-Version are headers; this call's own request_id is in the body, at
# cruxy.request_id.
curl -i https://api.cruxy.ai/api/v1/chat \
-H "Authorization: Bearer $CRUXY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "mira",
"messages": [
{ "role": "user", "content": "Hello" }
]
}'const res = await fetch('https://api.cruxy.ai/api/v1/chat', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CRUXY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'mira',
messages: [{ role: 'user', content: 'Hello' }],
}),
})
// Every response, errors included, carries both headers.
const correlationId = res.headers.get('X-Request-Id')
const version = res.headers.get('Cruxy-Version')
const body = await res.json()
if (res.ok) {
// A success returns its own per-call id. Request logs and feedback take it.
console.log(`request ${body.cruxy.request_id} (X-Request-Id ${correlationId}), Cruxy-Version ${version}`)
} else {
// An error's request_id equals the X-Request-Id header.
console.log(`${res.status} ${body.code}: request_id ${body.request_id}, Cruxy-Version ${version}`)
}The X-Request-Id header
X-Request-Id is a correlation id, sent on every response of every route,
whether the call succeeded or failed.
- You can set it. Send
X-Request-Idon the request, up to 128 characters of[A-Za-z0-9_-], and the gateway reuses your value. Any other value, or none, gets a new UUID. - On an error, it equals the body's
request_id. The JSON error envelope (code,error,request_id) carries the same value as the header. - On a success, it is not the call's id. A success returns a separate
per-call id,
cruxy.request_id, orrequest_idonPOST /api/v2/chat. Feedback and request logs take that per-call id, not the header. - On
POST /v1/chat/completionsandGET /v1/models, OpenAI's response and error shapes carry norequest_id, so the header is the only request id a response there carries.
Quote it when you report a problem; see Report a problem.
# Send your own correlation id. -D - prints the response headers, where
# X-Request-Id echoes it; -o /dev/null drops the body.
curl -s -o /dev/null -D - https://api.cruxy.ai/api/v1/models \
-H "Authorization: Bearer $CRUXY_API_KEY" \
-H "X-Request-Id: order-1234-attempt-1"const res = await fetch('https://api.cruxy.ai/api/v1/models', {
headers: {
Authorization: `Bearer ${process.env.CRUXY_API_KEY}`,
// Up to 128 characters of [A-Za-z0-9_-]; anything else gets a new UUID.
'X-Request-Id': 'order-1234-attempt-1',
},
})
console.log(res.headers.get('X-Request-Id')) // order-1234-attempt-1Where the request ID appears
A success returns its per-call id in the body. Where it sits depends on the endpoint:
| Response | Where the per-call id is |
|---|---|
POST /api/v1/chat, stream: false | cruxy.request_id. |
POST /api/v1/chat, stream: true | cruxy.request_id on the first frame, the role primer. |
POST /api/v2/chat | A top-level request_id in the body, and on the stream's start and error frames. |
POST /api/v1/embeddings and POST /api/v1/rerank | cruxy.request_id. |
POST /api/v1/moderations | The top-level id, formatted modr-<request_id>. Strip the modr- prefix for the same id the other endpoints return. |
An error carries request_id in the JSON error envelope, next to code and | |
error, and its value equals the response's X-Request-Id header. Device | |
| login and native sign-in return the same three fields while pending or refused. |
POST /v1/chat/completions and GET /v1/models, the plain OpenAI wire, have
no cruxy block, and their OpenAI error shape has no request_id field, so
X-Request-Id is the only id a response there carries. When you need a
per-call id that request logs and feedback accept, use POST /api/v1/chat.
Log the X-Request-Id of every response and the per-call request_id of every
success. The per-call id is the one that ties a line in your logs to the
gateway's record of the call.
The Cruxy-Version header
The API is versioned by date, and Cruxy-Version carries the date both ways:
- On the request,
Cruxy-Version: YYYY-MM-DDpins a version. Omit it to get the server's current default. - On the response,
Cruxy-Versionnames the version that served the call. Every response carries it, errors included.
A value that is not a YYYY-MM-DD date is 400 invalid_version. A
well-formed date this server does not support is 400 unknown_version, and
its message lists the supported dates. Both are answered before authentication
and before anything is billed.
A few request fields work only under a minimum version, and the
Changelog lists each one with its date. Sent
without that pin, file_ids is refused with 400 invalid_request, and the
error names the version you need in required_version.
A breaking change ships under a new dated version. Ordinary additive fields,
new cruxy.* fields among them, ship without one, so ignore JSON fields you do
not recognize.
Other response headers
| Header | Sent on | What it carries |
|---|---|---|
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset | Metered responses | The per-minute request window; see Rate limits and budgets. |
Retry-After | Every 429, and a 503 limits_unavailable | The seconds to wait before you retry. |
Cache-Control | Every JSON response | no-store: no cache, shared or private, may store the response. |
The cruxy block
Cruxy-native fields are additive and namespaced under a cruxy key, so an
OpenAI client parses the rest of the response unchanged. What the block holds
depends on the endpoint.
On chat
On POST /api/v1/chat, the block carries request_id, routed_tier, and
reasoning_effort for the turn. The rest appear only when they apply:
| Field | What it tells you |
|---|---|
request_id | The call's id. |
routed_tier | The tier that served the call. With model: "auto", this is the tier the router picked. |
reasoning_effort | The effort the call ran at, after the tier's default is applied. |
memory_recall | Stored memories were recalled into the turn: the scope and key of each, never the stored value. See Memory. |
memory_capture | The turn stored memory: how many memories, and the scope and key of each. See Memory. |
rules_applied | Standing rules were injected: how many, and from which rulesets. See Standing rules. |
files_omitted | Files attached to earlier messages were left out of this turn, each with the reason. |
undeclared_tool_replay | Replayed tool exchanges called tools your tools array does not declare, so the model read them as prose. See Handle tool calls. |
A stream spreads the block across frames. The role primer carries
request_id, routed_tier, and reasoning_effort, plus memory_recall,
rules_applied, and files_omitted when they apply. The usage frame carries
memory_capture, since what a turn stored is known only once the answer is
complete. Streaming shows the frames in
order.
Token counts and cost are not in the chat block: they are in usage, which
Usage and cost covers.
On other endpoints
- Embeddings and rerank:
cruxycarriesrequest_idandcost. - Moderations: no top-level block. A flagged result carries its own
results[].cruxy.hazard_codes, the guard taxonomy codes behind the flag, and an unflagged result has nocruxykey; see Moderations. POST /api/v2/chat: nocruxywrapper. The same signals are top-level fields:request_id,routing(the tier, how it was routed, and the effort),memory_recall,memory_capture,rules_applied,files_omitted, andundeclared_tool_replay.POST /v1/chat/completions: none. That wire carries no Cruxy-native fields.
New cruxy.* fields ship without a new API version. Ignore a field you do not
recognize rather than failing on it.
Look up a request
An organization owner or admin can read a call's metadata by its per-call id,
the cruxy.request_id a success returned, with GET /api/v1/logs/{request_id}.
# REQUEST_ID holds the cruxy.request_id of an earlier call.
curl https://api.cruxy.ai/api/v1/logs/$REQUEST_ID \
-H "Authorization: Bearer $CRUXY_API_KEY"// REQUEST_ID holds the cruxy.request_id of an earlier call.
const requestId = process.env.REQUEST_ID
if (!requestId) throw new Error('set REQUEST_ID to the cruxy.request_id of an earlier call')
const res = await fetch(`https://api.cruxy.ai/api/v1/logs/${requestId}`, {
headers: { Authorization: `Bearer ${process.env.CRUXY_API_KEY}` },
})
if (!res.ok) {
const { code, request_id } = await res.json()
throw new Error(`log lookup failed: ${res.status} ${code} (request_id ${request_id})`)
}
const log = await res.json()
console.log(log.status, log.model, log.input_tokens, log.output_tokens, log.latency_ms){
"object": "request_log",
"request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "success",
"model": "kavi",
"input_tokens": 1024,
"output_tokens": 256,
"latency_ms": 842,
"key_id": "6f9c1e2a-1b2c-4d5e-8f90-1a2b3c4d5e6f",
"workspace_id": "2b7c9e4a-3d5f-4a6b-8c1d-9e0f1a2b3c4d",
"created_at": "2026-07-18T12:34:56.000Z"
}- Metadata only. The model, token counts, latency, the lifecycle
status(success,error, orpartial), the key and workspace that made the call, and when. Prompt and completion content are never stored or returned. - Owner or admin only. Anyone else gets
403logs_forbidden. - One answer for a missing id. An id from another organization, or one that
names nothing, is
404log_not_found, so the response never confirms that an id exists. - The most recent row wins. One id can map to more than one record, such as
a reused
Idempotency-Keyor an error followed by a retried success; the most recent is returned.
GET /api/v1/logs lists the same metadata for every key in your organization,
newest first.
Report a problem
When you contact Cruxy support about a call, send enough to find it without a round of questions:
- The ids, whole and exactly as the response returned them: the
X-Request-Idheader, and for a success its per-callrequest_idas well. If you retried, send the ids of every attempt you have. - The
Cruxy-Versionthe response named. - The method and path, such as
POST /api/v1/chat, and whetherstreamwas on. - The HTTP status and the
code. Theerrormessage alone is not enough; its wording can change. - When it happened, in UTC.
- For a stream, whether it reached
data: [DONE], or afinishframe onPOST /api/v2/chat. - The
key_id, if the problem is with a key. Never send the key itself.
To tell Cruxy how a call turned out, rather than report a fault, send
Feedback joined to the same request_id.
Next steps
- Usage and cost: the token counts and cost on each response.
- Errors and retries: the error envelope and every code.
- Feedback: attach an outcome to a
request_id. - Changelog: the dated versions and what each one changed.