Reliability

Run Cruxy in production: per-tier deadlines, which errors to retry and how, Idempotency-Key, telling a finished stream from a failed one, and status.

This page collects what the gateway guarantees when a call is slow or fails, and what it leaves to your client. Each section links to the page that covers its topic in full. The sample streams a long kavi turn, accepts the answer only once the stream finishes, and retries a failed attempt:

# One key per logical request: reuse it if you send this request again.
IDEMPOTENCY_KEY=$(uuidgen)
 
# A long kavi turn: stream it. --max-time sits above the five-minute limit
# on a stream, so the gateway, not curl, decides when the turn has run long.
curl -sN --max-time 330 https://api.cruxy.ai/api/v1/chat \
  -H "Authorization: Bearer $CRUXY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -d '{
    "model": "kavi",
    "stream": true,
    "messages": [
      { "role": "user", "content": "Plan a three-day walking tour of Old Delhi." }
    ]
  }' > stream.txt
 
# The completion finished only if the stream ended with [DONE].
grep -q '^data: \[DONE\]' stream.txt && echo "complete" || echo "incomplete: send it again"

Deadlines and timeouts

With stream: false, the gateway makes one attempt at the model, bounded by the tier's deadline:

TierDeadline
kavi90 seconds
vaani90 seconds
mira45 seconds

An attempt that runs past its deadline is cancelled and answered 504 upstream_timeout. Nothing is billed, and the gateway does not retry it, since a turn that needed longer needs longer again. The same deadlines apply on all three chat wires: POST /api/v1/chat, POST /api/v2/chat, and POST /v1/chat/completions.

  • Stream long turns. A stream may run for up to five minutes. A long kavi turn, with reasoning on and a large output, belongs on a stream.
  • Or ask for less. A lower max_tokens or reasoning_effort shortens the turn; see Reasoning effort.
  • Set your client timeout above the deadline, or above five minutes for a stream, so a slow model reaches you as a 504 with a request_id rather than as your own abort.

What the gateway retries for you

  • A failing model, inside the gateway. An overloaded or failing model, or a dropped connection to it, is retried a bounded number of times, with backoff, before the gateway answers 503 upstream_overloaded or model_unavailable. On a stream, that happens only while the stream opens, never after a byte has reached you.
  • Never a non-streaming timeout. See Deadlines and timeouts.
  • Never on another tier. Once a call is sent, it stays on its tier. model: "auto" picks a healthy tier before the call, and an explicit tier that keeps failing is refused at once with 503 model_unavailable while it cools down. See Auto routing.

What to retry

Branch on the error's code, never on its message:

ResponseWhat to do
429 rate_limited, credits_unavailable, headless_concurrency_limit, or 503 limits_unavailableWait the Retry-After seconds, then retry.
503 upstream_overloaded, model_unavailable, all_models_unavailable, server_shutting_down, or 500 internal_errorBack off, then retry.
504 upstream_timeoutChange the request first: stream it, or lower max_tokens or reasoning_effort.
429 budget_exhausted, key_spend_cap_exceeded, workspace_spend_cap_exceeded, credits_exhaustedStop. Each clears only at its reset_at, or when the cap changes.
No response, or a stream that ended before it finishedSend the request again, with the same Idempotency-Key.
Any other codeLook it up in Errors and retries. Most describe the request or the key, and fail the same way until you change them.

When you retry:

  • Honour Retry-After. Every 429 sends it, and so does a 503 limits_unavailable.
  • Otherwise back off exponentially, with jitter, so many clients that failed together do not retry together.
  • Cap the attempts and the total wait, so a persistent failure ends instead of hanging.
  • Log the request_id of every failure, retried or not; see Request IDs and metadata.

Retry safely with Idempotency-Key

Send an Idempotency-Key header, one per logical request, and reuse it on every retry of that request:

  • A success is stored for 24 hours. A later or concurrent call with the same key and the same body replays it byte for byte, without calling the model and without billing again. A concurrent duplicate waits for the first call to finish.
  • Only a success is stored. An error, a 429 or any 5xx included, releases the key, so a retry with the same key and body runs the request again. That is the intended way to retry.
  • A different body under the same key is 409 idempotency_conflict.
  • A response over 256 KiB is served but not stored, so a retry runs again. It is still billed once.
  • A key is at most 255 characters, and is scoped to your API key and the endpoint.
  • A stream stores and replays nothing. A retry with the same key and body streams a new generation. When both attempts complete, the request is billed once; an attempt that did not complete is billed for the tokens it delivered, and the retry is billed as well.

Errors and retries lists the endpoints that accept the header.

Confirm a stream finished

Once a stream opens, its 200 is already sent, so a failure after that point cannot change the status. How the stream tells you it failed depends on the wire:

  • POST /api/v1/chat: data: [DONE] is the only success signal. A failure after the stream opens sends no error frame; the stream stops early. Treat a stream that ends before [DONE] as a failed completion: discard the partial output or retry, and never show it as a finished answer.
  • POST /api/v2/chat: the stream always ends with exactly one finish or error frame. It has no [DONE].
  • POST /v1/chat/completions: a failure after the stream opens ends it with a data: {"error": {...}} frame instead of [DONE].

The tokens a failed stream delivered are billed as a partial usage row. Streaming shows how to detect each case.

Check platform status

status.getcruxy.com is the Cruxy status page. For the same picture in code, GET /api/status returns a status document. It takes no key.

curl https://api.cruxy.ai/api/status
  • status is the whole platform in one word: healthy, degraded, down, or unknown.
  • components has a row for the API, the database, each chat tier, and the cruxy-embed, cruxy-rerank, and cruxy-guard models, each with its own status and when it was last checked. A component that is measurably down is a 200 carrying down; the document is a 503 only when no model component has a fresh measurement.
  • The verdicts come from active probes, not from live traffic, and the response is never cached.

GET /api/health reports only that the gateway's HTTP listener is up. It checks no model or database, so do not read it as platform status.

Production checklist

  • Use a developer API key on a server. See Get your API key.
  • Pin Cruxy-Version, and ignore JSON fields you do not recognize.
  • Set client timeouts above the tier deadline, or above five minutes for a stream.
  • Stream long kavi turns, and size max_tokens for the thinking as well as the answer.
  • Send an Idempotency-Key on every request that takes one, and reuse it on that request's retries.
  • Retry only what the table above says, honouring Retry-After, with a cap on attempts.
  • Check for [DONE], or a finish frame on v2, before you show a streamed answer.
  • Log the X-Request-Id, the request_id, and the Cruxy-Version of every call.
  • Read Known limitations before you launch.

Next steps

Was this page helpful?