Guide
Error Codes
Every error of the Railwail API with its HTTP status, code, cause, fix and whether a retry helps. All errors share OpenAI's JSON format, so the OpenAI SDKs raise them as their usual exceptions.
- Body
- {"error": {message, type, code}}
- Codes
- 38 codes · 13 statuses
- Retry helps
- 429 limits, 5xx
- Never retry
- 400, 401, 402, 403, 404
Error Response Format
message is for humans, code is for your program: switch on code, never on the message. Chat completions add param (the field at fault, or null); strict-schema routes add details with the field errors.
{
"error": {
"message": "Invalid API key",
"type": "authentication_error",
"code": "invalid_api_key"
}
}{
"error": {
"message": "n > 1 is not supported; send one request per completion.",
"type": "invalid_request_error",
"param": "n",
"code": "invalid_request"
}
}{
"error": {
"type": "invalid_request_error",
"code": "validation_failed",
"message": "Request validation failed",
"details": {
"formErrors": ["Unrecognized key(s) in object: 'aspect_ratio'"],
"fieldErrors": {}
}
}
}See one for yourself, free: this call answers with the 401 above and uses no credits.
curl -s "https://railwail.com/api/v1/models?limit=1" \
-H "Authorization: Bearer rw_invalid"Not errors: image, embedding and video requests may answer 202 with status: "processing" and a Location: /api/v1/jobs/<id> header. Poll that job.
HTTP Status Codes
Filter by status, code or word. Every code has its own anchor, e.g. #trial_limit, so you can link straight to it.
- 400
The request body is not valid JSON.
Lösung Send a JSON object with Content-Type: application/json.
Nein: erst beheben - 400
The body failed a check;
paramnames the field. Rejected on purpose: n > 1, logprobs / top_logprobs, audio output or non-text modalities, the deprecated functions / function_call, web_search_options, non-function tools, a tool_choice naming a function that is not in tools, role 'function', a tool message without tool_call_id.Lösung Fix the field named in
param. One request per completion instead of n > 1; tools / tool_choice instead of functions.Nein: erst beheben - 400
Strict schema: an unknown or invalid field.
detailsholds the zod field errors (for example aspect_ratio or seed on images).Lösung Remove or correct the fields listed in
details.Nein: erst beheben - 400
An image field (image_url, start_image or end_image) that the model has no input for. image_url and start_image are the same first frame.
Lösung Send only the image fields the model's page lists, or pick an image-to-video model. Nothing was charged. → /docs/api/video-generation#request
Nein: erst beheben - 400
end_image was sent without image_url or start_image.
Lösung Send the first frame too, or leave out end_image. Nothing was charged. → /docs/api/video-generation#request
Nein: erst beheben - invalid_value400unsupported_parameterinvalid_value
invalid_request_error
embeddings, audio/transcriptions
Nein: erst beheben
dimensions on a model that cannot be shortened (only text-embedding-3-small and -large can), or a value above the model's size (invalid_value). Transcriptions: a SeamlessM4T model without a language it supports (invalid_value).
Lösung Leave out dimensions, or send at most 1,536 (-small) or 3,072 (-large). For SeamlessM4T send the language of the audio as ISO-639-1, e.g. en. → /docs/api/embeddings#request
Nein: erst beheben - 400
The provider refused an input: more tokens than the model accepts, an empty text, or too many inputs. The message gives the reason in our words.
Lösung Split long texts or send fewer inputs per request. The run was refunded. → /docs/api/embeddings#errors
Nein: erst beheben - missing_fileempty_file400invalid_bodymissing_fileempty_file
invalid_request_error
audio/transcriptions, audio/uploads
Nein: erst beheben
The multipart body could not be read, has no
filepart, or the file is empty.Lösung Send multipart/form-data with a non-empty
fileand amodelfield.Nein: erst beheben - invalid_provider400
A category or provider filter on GET /models that is not in the catalog's lists (until 25.09.2026 this answered 500).
paramnames the filter.Lösung Use one of the values the message lists, or leave the filter out. → /docs/api/models
Nein: erst beheben - 400
A text, code or vision model that no chat provider serves (the Replicate-hosted ones, e.g. CodeLlama or Llama vision).
Lösung Use a chat model from OpenAI, Anthropic, Google or DeepSeek, or run the model on its model page. → /docs/api/chat-completions#models
Nein: erst beheben - 400
A content part this provider cannot take, or one without a known price: DeepSeek accepts text only, Gemini needs images as base64 data URLs, files only as inline PDFs (file.file_data as a data URL, no file_id) with a readable page count, no audio parts (input_audio).
Lösung Send text only, send the image or PDF as a data URL, transcribe audio first, or pick a model that takes it. → /docs/api/chat-completions#vision
Nein: erst beheben - 400
The model provider refused the request (HTTP 400, 413 or 422), for example a prompt above the context window or a schema the provider does not accept. Its message is passed through (up to 500 characters).
Lösung Read the message, shorten the input or adjust the parameters.
Nein: erst beheben - 401
The key is missing, unknown, revoked or expired. The message is "Invalid API key".
Lösung Send Authorization: Bearer rw_live_… with an active key. ↗ /dashboard/settings/api-keys
Nein: erst beheben - 402
The balance is lower than the credits this request holds up front (for chat: the input estimate plus max_tokens output tokens). Checked before any provider call.
Lösung Top up your balance, or lower max_tokens. ↗ /dashboard/billing
Nach dem Aufladen - 402
Your monthly spending limit would be exceeded by this request.
Lösung Raise or remove the limit under Spend limits, or wait for the next month. ↗ /dashboard/settings/spend-limits
Nein: erst beheben - 402monthly_limit_exceeded
spending_limit_error
images, videos, embeddings, audio/speech, audio/transcriptions
Nein: erst beheben
The same monthly spending limit as spending_limit_reached, reported with a different code on these routes.
Lösung Raise or remove the limit under Spend limits. ↗ /dashboard/settings/spend-limits
Nein: erst beheben - 403
The key lacks the scope of this route; the message names it. New keys get read + chat only, so images, video, audio and embeddings need their scope or "all".
Lösung Create a new key with that scope or "all" (a key's scopes cannot be edited after creation). → /docs/authentication#scopes
Nein: erst beheben - 403
The key has an IP allowlist and this address is not on it. The message names the address the API saw.
Lösung Add the address (or its CIDR block) to a new or rotated key, or call from an allowed host. → /docs/authentication#ip-allowlist
Nein: erst beheben - 403
The job belongs to another account.
Lösung Poll only jobs created with a key of your account.
Nein: erst beheben - 404model_not_found
invalid_request_error
chat/completions, images, embeddings, videos, audio, models/{slug}
Nein: erst beheben
No active model has this slug (or provider model id). On chat/completions also for image, video and audio slugs: they are not chat models.
Lösung Use a slug from the model pages or GET /api/v1/models. → /models
Nein: erst beheben - 404
Unknown job id. Job ids are UUIDs.
Lösung Use the id from X-Railwail-Job-Id, the part after chatcmpl-, or job_id in a 202 answer.
Nein: erst beheben - 413
The uploaded file is larger than 25 MB (transcriptions; a request over 26 MB is refused before it is read) or 10 MB (voice samples on audio/uploads).
Lösung Split or compress the audio.
Nein: erst beheben - type_mismatch415
The voice sample is not MP3, WAV, WebM or Ogg (checked on the file's bytes), or its declared type does not match its content.
Lösung Export the clip as MP3 or WAV and upload it again. → /docs/api/audio-speech#voice-cloning
Nein: erst beheben - audio_unreadable400
The voice sample is longer than 60 seconds, or its length could not be read from the file.
Lösung Trim the clip to at most 60 seconds (10-30 s of clear speech works best); if unreadable, export it as MP3 or WAV. → /docs/api/audio-speech#voice-cloning
Nein: erst beheben - 400
A voice_sample was sent without consent: true.
Lösung Clone only your own voice or one you have permission to use, and confirm it with consent: true. Nothing was charged. → /docs/api/audio-speech#voice-cloning
Nein: erst beheben - 400
voice_sample is not the https url of your own upload from POST /api/v1/audio/uploads (other hosts, other accounts and image uploads are refused).
Lösung Upload the clip with POST /api/v1/audio/uploads and pass the returned url within 24 hours. → /docs/api/audio-speech#voice-cloning
Nein: erst beheben - voice_sample_required400
voice_sample was sent to a model without a reference-voice input (message: model does not support voice cloning), or a model that only speaks in a cloned voice got no voice_sample.
Lösung Pick a voice-cloning model for voice_sample, or send voice_sample and consent to a model that needs one. → /docs/api/audio-speech#voice-cloning
Nein: erst beheben - 413
The request would return more than 2,000,000 values (inputs × dimensions).
Lösung Send fewer inputs per request, or ask for fewer dimensions. Nothing was charged. → /docs/api/embeddings#request
Nein: erst beheben - 429
The key's own requests-per-minute limit (60-6,000, default 600) is used up.
Lösung Wait X-RateLimit-Reset seconds. There is no Retry-After header. → /docs/guides/rate-limits
Nach X-RateLimit-Reset - 429api_key_limit_exceeded
rate_limit_error
chat/completions, images, videos, audio/speech, audio/transcriptions, embeddings
Nach X-Key-Limit-Reset
The run would take the key past its own daily or monthly credit limit (set per key on the API keys page). Nothing was created or charged; for images with
n> 1, none of them. This is not the requests-per-minute limit.Lösung Do not retry before the instant in
X-Key-Limit-Reset(ISO 8601, UTC: the next 00:00 UTC for the daily limit, the 1st of the next month for the monthly one); X-RateLimit-Reset does not apply. Or raise the key's limit under Limits & alerts. ↗ /dashboard/settings/api-keysNach X-Key-Limit-Reset - 429
More than 20 voice sample uploads in one hour (per account, website and API together).
Lösung Wait the Retry-After seconds; reuse an uploaded url for 24 hours instead of uploading the same clip again.
Ja, mit Backoff - 429
A trial rule for accounts without a purchase: runs over 2 credits, more than 5 runs in 24 h, 3 failures in a row, more than 8 trial runs per IP in 24 h, or an account younger than 24 h.
Lösung Top up once to lift the trial rules, or lower max_tokens. ↗ /dashboard/billing
Nach dem Aufladen - 429
The model provider is throttling requests.
Lösung Retry with exponential backoff, or use another model.
Ja, mit Backoff - 499
The client closed the stream before the first chunk. Tokens generated until then are billed, the rest of the hold is refunded.
Lösung Nothing to fix; your client ended the request.
entfällt - 500
Unexpected server error.
Lösung Retry once. If it persists, write to info@railwail.com with the time of the request (and for chat the X-Railwail-Job-Id).
Ja, mit Backoff - 502
The provider answered with a 5xx or the connection to it failed (embeddings: an answer without readable vectors). A failed run is refunded in full.
Lösung Retry with exponential backoff.
Ja, mit Backoff - 504
The provider did not answer within 85 seconds. The run was stopped and refunded; there is no background job to poll.
Lösung Retry, or send fewer inputs per request.
Ja, mit Backoff
38 von 38 Codes
Retry policy
Retry with backoff
upstream_rate_limited, 502 upstream_error, 503 model_unavailable (or switch model), 500 internal_error once. Exponential backoff with jitter.Wait, then retry
rate_limit_exceeded: your key's own limit. Wait X-RateLimit-Reset seconds (there is no Retry-After). 429 api_key_limit_exceeded: the key's daily or monthly credit limit; not before the time in X-Key-Limit-Reset, or raise the limit. Rate limitsTop up first
insufficient_credits and 429 trial_limit (trial accounts: runs over 2 credits, 5 runs per 24 h, 3 failures in a row). BillingNever retry unchanged
spending_limit_reached: the same request fails again. Fix the request, key, scope or limit.The OpenAI SDKs already retry 429 and 5xx twice; that is harmless for the transient codes and useless for trial_limit and api_key_limit_exceeded (the refused attempts cost nothing). Over plain HTTP, a helper that follows the policy:
const RETRYABLE = new Set([
"rate_limit_exceeded", // your key's limit: wait X-RateLimit-Reset
"upstream_rate_limited", // the provider throttles
"upstream_error", // provider 5xx / connection
"model_unavailable", // may be temporary; try another model after a few attempts
"internal_error",
// not "api_key_limit_exceeded": the key's daily/monthly credit limit frees up only at X-Key-Limit-Reset
]);
async function chatWithRetry(body: object, tries = 4): Promise<any> {
for (let attempt = 1; ; attempt++) {
const res = await fetch("https://railwail.com/api/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RAILWAIL_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
const data = await res.json();
// A slow non-stream run can fail after the 200 status was sent: check the body too.
if (res.ok && !data.error) return data;
const code: string = data.error?.code ?? `http_${res.status}`;
if (!RETRYABLE.has(code) || attempt >= tries) {
throw new Error(`${res.status} ${code}: ${data.error?.message ?? ""}`);
}
const reset = Number(res.headers.get("x-ratelimit-reset"));
const waitMs =
code === "rate_limit_exceeded" && reset > 0 ? reset * 1000 : 2 ** attempt * 500 + Math.random() * 250;
await new Promise((resolve) => setTimeout(resolve, waitMs));
}
}
const data = await chatWithRetry({
model: "gpt-4o-mini",
max_tokens: 300,
messages: [{ role: "user", content: "Hello" }],
});
console.log(data.choices[0].message.content);import os
import random
import time
import requests
RETRYABLE = {
"rate_limit_exceeded", # your key's limit: wait X-RateLimit-Reset
"upstream_rate_limited", # the provider throttles
"upstream_error", # provider 5xx / connection
"model_unavailable", # may be temporary; try another model after a few attempts
"internal_error",
# not "api_key_limit_exceeded": the key's daily/monthly credit limit frees up only at X-Key-Limit-Reset
}
def chat_with_retry(body: dict, tries: int = 4) -> dict:
for attempt in range(1, tries + 1):
res = requests.post(
"https://railwail.com/api/v1/chat/completions",
headers={"Authorization": f"Bearer {os.environ['RAILWAIL_API_KEY']}"},
json=body,
timeout=300,
)
data = res.json()
# A slow non-stream run can fail after the 200 status was sent: check the body too.
if res.ok and "error" not in data:
return data
error = data.get("error") or {}
code = error.get("code") or f"http_{res.status_code}"
if code not in RETRYABLE or attempt == tries:
raise RuntimeError(f"{res.status_code} {code}: {error.get('message', '')}")
reset = int(res.headers.get("x-ratelimit-reset") or 0)
time.sleep(reset if code == "rate_limit_exceeded" and reset > 0 else 2 ** attempt * 0.5 + random.random() / 4)
data = chat_with_retry({
"model": "gpt-4o-mini",
"max_tokens": 300,
"messages": [{"role": "user", "content": "Hello"}],
})
print(data["choices"][0]["message"]["content"])Errors in streams and slow answers
- Before the first byte of a stream: a normal HTTP error status and body, and the hold is refunded in full.
- After the first byte: an SSE event
data: {"error": {…}}withoutdata: [DONE]. The OpenAI SDKs raise it asAPIError. - You close the stream: tokens generated so far are billed, the rest of the hold is refunded; before the first chunk the status is 499
client_closed_request. - Non-stream answers slower than 25 s: the server keeps the connection alive with whitespace, so the status is already 200; a later failure arrives as an
{"error": …}body with HTTP 200. Check the body, or usestream: true.
What an error costs
Errors cost nothing. A request refused before the run reserves nothing; a run that fails at the provider (400 provider_rejected_request, 429 upstream_rate_limited, 5xx, an error inside a stream) is refunded in full. Billed are only a stream you close yourself (for the tokens generated until then), a non-stream request whose connection you drop (it runs to the end), and a GPU run stopped at its time limit. Holds and refunds show in Billing.
Known differences between routes
The routes are not fully uniform yet. Until they are, handle these as they are:
- Monthly spending limit:
spending_limit_reached(typeinsufficient_credits_error) on chat,monthly_limit_exceeded(typespending_limit_error) on videos and audio, and 500internal_erroron images and embeddings. GET /api/v1/modelsanswers 500 for an invalidcategoryorproviderfilter instead of 400.GET /api/v1/jobs/{id}answers another account's job with 403forbiddenof typeauthentication_error.- Rate-limit headers are missing on 202 answers of images and embeddings, and on
GET /modelsandGET /jobs.
Error Handling in Code
With the railwail npm SDK every JSON error becomes a RailwailError; timeouts, network failures and non-JSON bodies throw their own errors. With the OpenAI SDKs, APIError (TypeScript) or APIStatusError (Python) carries the same code.
import railwail, { RailwailError } from "railwail";
const rw = railwail(process.env.RAILWAIL_API_KEY!);
try {
await rw.chat("gpt-4o-mini", [{ role: "user", content: "Hello" }], { max_tokens: 300 });
} catch (err) {
if (err instanceof RailwailError) {
console.error(err.status, err.type, err.code, err.message);
} else {
throw err; // AbortError (timeout), TypeError (network), SyntaxError (non-JSON body)
}
}