Skip to content
Docs navigation: Open models / Open-model retries

Open-model retries

Open modelsOpen-model retries Updated

On this page

Send an Idempotency-Key header when a lost response or a network failure might make you retry. It is optional, it works on streaming and non-streaming requests alike, and it guarantees one generation and one charge. The same header and replay rules apply to /v1/messages.

ConnectionValue
API basehttps://api.runinfra.ai/v1
Workspace keyrp_...
Process variableRUNINFRA_GATEWAY_KEY
BillingThe existing funding rules of your open-model account.

Replace MODEL_ID with an available id from Open models; read current capabilities, limits and prices from GET /v1/models.

Use a non-blank printable ASCII value of 255 characters or fewer after trimming. One fresh key per logical request, reused only when you are retrying that same request. A UUID generated at the call site is a convenient source.

cURL
curl https://api.runinfra.ai/v1/chat/completions \
  -H "Authorization: Bearer $RUNINFRA_GATEWAY_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: YOUR_STABLE_IDEMPOTENCY_KEY" \
  -d '{"model":"MODEL_ID","messages":[{"role":"user","content":"Write one sentence."}],"max_tokens":64}'

What a reused key does

Idempotent retry outcomes replay a stored response with a replay header, return 409 while the first request is still running, return 400 for different request parameters, and return 422 when the stored response is too large to replay; a streamed request with the same key replays its terminal receipt instead of generating again.

  • Reuse rules
  • Returns the stored response
  • X-RunInfra-Idempotent-Replay: true
  • 409 idempotency_conflict
  • The first request is still running
  • Retry-After: 1
  • Retry with the same key.
  • 400 idempotency_mismatch
  • The key was used with different request parameters
  • Use a different key only for a new logical request.
  • 422 idempotency_replay_unavailable
  • The stored response is too large to replay
  • Do not retry with a new key because that can start new inference work.
  • A streamed request replays its terminal receipt, never the delivered tokens.
  • Idempotency-Key
  • guarantees one generation and one charge.

Reuse the same key only with the same HTTP method, route, and request body. A key sent with a different body is a different logical request, and it is refused with 400 idempotency_mismatch rather than answered with the wrong reply.

A non-streamed response up to 6 MiB is held for 24 hours; a larger one answers 422 idempotency_replay_unavailable.

Retrying a stream

A stream is not replayed byte for byte. We do not store the tokens we deliver, so there is nothing to send again. What the key protects on a streaming request is the part that costs money.

SituationWhat you get
The original stream is still open409 idempotency_conflict with Retry-After: 1. No second generation starts.
The original stream finished and settled200 with X-RunInfra-Idempotent-Replay: true and a non-streaming JSON receipt, or a short event stream on a streaming /v1/messages retry. Nothing is regenerated and nothing is billed again.
The original delivered nothing and was billed nothingThe key is released, so a retry runs normally, exactly as a request without a key would.

The receipt is an ordinary chat completion envelope, so a chat-compatible client can parse it. content is null because the delivered text was never stored. finish_reason is length or content_filter when the original reply was cut short, and null otherwise. The replay facts ride in idempotent_replay.

JSON
{
  "id": "chatcmpl-8f0a1c2e-...",
  "object": "chat.completion",
  "created": 1786800000,
  "model": "MODEL_ID",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": null },
      "finish_reason": null
    }
  ],
  "usage": {
    "prompt_tokens": 1000,
    "completion_tokens": 500,
    "total_tokens": 1500,
    "prompt_tokens_details": { "cached_tokens": 200 },
    "cost": 0.000282,
    "runinfra": { "cost_microcents": 28200, "cached_input_tokens": 200 }
  },
  "idempotent_replay": {
    "replayed": true,
    "streamed": true,
    "content_replayed": false,
    "regenerated": false,
    "billed_again": false,
    "original_status": 200,
    "stream_outcome": "completed",
    "usage_basis": "provider_reported",
    "cost_microcents": 28200,
    "detail": "The original request with this Idempotency-Key streamed to completion and settled once. Streamed tokens are not stored, so this reply carries the terminal usage and cost instead of the generated text. Nothing was regenerated and nothing was billed a second time."
  }
}

X-RunInfra-Idempotent-Replay is the signal on every route: read it before handing a response to a streaming parser. /v1/chat/completions and /v1/messages return the idempotent_replay object; on /v1/responses the reply is converted to a Responses object and only the header survives. A reply the original cut short keeps status: "incomplete" and its incomplete_details.reason there, as the original stream did.

If you need the generated text after a dropped stream, send a new request with a new key. That is new paid inference. Reusing the old key returns the receipt, not the text.

Only one charge per key

A retried request is charged once, streamed or not: a duplicate settlement for the same key is refused rather than billed twice. The key holds for the whole time a request can run, so a retry sent while a slow non-streaming request is still generating gets 409 idempotency_conflict and never starts a second generation.

When a key cannot be checked, a keyed request is refused rather than run without protection. Every endpoint, chat completions, Responses and messages included, returns 503 idempotency_unavailable with Retry-After before any inference starts or any charge is made; retry with the same key. On /v1/messages the same refusal arrives as Messages protocol's overloaded_error, which the Messages protocol SDKs retry. A request without an Idempotency-Key is never affected.

Client request ids

X-Client-Request-Id is an optional correlation header. The gateway echoes it back on the response. It is not a substitute for Idempotency-Key. Use a non-blank printable ASCII value of 512 characters or fewer. Any other value is refused with 400 invalid_trace_header.

Source reference