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"
  }
}

See one for yourself, free: this call answers with the 401 above and uses no credits.

try it
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
    invalid_json

    invalid_request_error

    all POST routes

    No: fix first

    The request body is not valid JSON.

    Fix Send a JSON object with Content-Type: application/json.

  • 400
    invalid_request

    invalid_request_error

    chat/completions

    No: fix first

    The body failed a check; param names 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.

    Fix Fix the field named in param. One request per completion instead of n > 1; tools / tool_choice instead of functions.

  • 400
    validation_failed

    invalid_request_error

    images, embeddings, videos, audio/speech

    No: fix first

    Strict schema: an unknown or invalid field. details holds the zod field errors (for example aspect_ratio or seed on images).

    Fix Remove or correct the fields listed in details.

  • 400
    unsupported_image_input

    invalid_request_error

    videos

    No: fix first

    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.

    Fix 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

  • 400
    end_image_without_start_image

    invalid_request_error

    videos

    No: fix first

    end_image was sent without image_url or start_image.

    Fix Send the first frame too, or leave out end_image. Nothing was charged. → /docs/api/video-generation#request

  • invalid_value
    400
    unsupported_parameterinvalid_value

    invalid_request_error

    embeddings, audio/transcriptions

    No: fix first

    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).

    Fix 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

  • 400
    provider_rejected_input

    invalid_request_error

    embeddings

    No: fix first

    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.

    Fix Split long texts or send fewer inputs per request. The run was refunded. → /docs/api/embeddings#errors

  • missing_fileempty_file
    400
    invalid_bodymissing_fileempty_file

    invalid_request_error

    audio/transcriptions, audio/uploads

    No: fix first

    The multipart body could not be read, has no file part, or the file is empty.

    Fix Send multipart/form-data with a non-empty file and a model field.

  • invalid_provider
    400
    invalid_categoryinvalid_provider

    invalid_request_error

    models

    No: fix first

    A category or provider filter on GET /models that is not in the catalog's lists (until 25.09.2026 this answered 500). param names the filter.

    Fix Use one of the values the message lists, or leave the filter out. → /docs/api/models

  • 400
    model_not_supported

    invalid_request_error

    chat/completions

    No: fix first

    A text, code or vision model that no chat provider serves (the Replicate-hosted ones, e.g. CodeLlama or Llama vision).

    Fix Use a chat model from OpenAI, Anthropic, Google or DeepSeek, or run the model on its model page. → /docs/api/chat-completions#models

  • 400
    unsupported_content

    invalid_request_error

    chat/completions

    No: fix first

    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).

    Fix 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

  • 400
    provider_rejected_request

    invalid_request_error

    chat/completions

    No: fix first

    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).

    Fix Read the message, shorten the input or adjust the parameters.

  • 401
    invalid_api_key

    authentication_error

    all routes with a key

    No: fix first

    The key is missing, unknown, revoked or expired. The message is "Invalid API key".

    Fix Send Authorization: Bearer rw_live_… with an active key. ↗ /dashboard/settings/api-keys

  • 402
    insufficient_credits

    insufficient_credits_error

    all generating routes

    After a top-up

    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.

    Fix Top up your balance, or lower max_tokens. ↗ /dashboard/billing

  • 402
    spending_limit_reached

    insufficient_credits_error

    chat/completions

    No: fix first

    Your monthly spending limit would be exceeded by this request.

    Fix Raise or remove the limit under Spend limits, or wait for the next month. ↗ /dashboard/settings/spend-limits

  • 402
    monthly_limit_exceeded

    spending_limit_error

    images, videos, embeddings, audio/speech, audio/transcriptions

    No: fix first

    The same monthly spending limit as spending_limit_reached, reported with a different code on these routes.

    Fix Raise or remove the limit under Spend limits. ↗ /dashboard/settings/spend-limits

  • 403
    insufficient_scope

    permission_error

    all routes with a key

    No: fix first

    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".

    Fix Create a new key with that scope or "all" (a key's scopes cannot be edited after creation). → /docs/authentication#scopes

  • 403
    ip_not_allowed

    permission_error

    all routes with a key

    No: fix first

    The key has an IP allowlist and this address is not on it. The message names the address the API saw.

    Fix Add the address (or its CIDR block) to a new or rotated key, or call from an allowed host. → /docs/authentication#ip-allowlist

  • 403
    forbidden

    authentication_error

    jobs/{id}

    No: fix first

    The job belongs to another account.

    Fix Poll only jobs created with a key of your account.

  • 404
    model_not_found

    invalid_request_error

    chat/completions, images, embeddings, videos, audio, models/{slug}

    No: fix first

    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.

    Fix Use a slug from the model pages or GET /api/v1/models. → /models

  • 404
    job_not_found

    invalid_request_error

    jobs/{id}

    No: fix first

    Unknown job id. Job ids are UUIDs.

    Fix Use the id from X-Railwail-Job-Id, the part after chatcmpl-, or job_id in a 202 answer.

  • 413
    file_too_large

    invalid_request_error

    audio/transcriptions, audio/uploads

    No: fix first

    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).

    Fix Split or compress the audio.

  • type_mismatch
    415
    unsupported_typetype_mismatch

    invalid_request_error

    audio/uploads

    No: fix first

    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.

    Fix Export the clip as MP3 or WAV and upload it again. → /docs/api/audio-speech#voice-cloning

  • audio_unreadable
    400
    audio_too_longaudio_unreadable

    invalid_request_error

    audio/uploads

    No: fix first

    The voice sample is longer than 60 seconds, or its length could not be read from the file.

    Fix 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

  • 400
    invalid_voice_sample

    invalid_request_error

    audio/speech

    No: fix first

    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).

    Fix Upload the clip with POST /api/v1/audio/uploads and pass the returned url within 24 hours. → /docs/api/audio-speech#voice-cloning

  • voice_sample_required
    400
    voice_cloning_unsupportedvoice_sample_required

    invalid_request_error

    audio/speech

    No: fix first

    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.

    Fix 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

  • 413
    too_many_embedding_values

    invalid_request_error

    embeddings

    No: fix first

    The request would return more than 2,000,000 values (inputs × dimensions).

    Fix Send fewer inputs per request, or ask for fewer dimensions. Nothing was charged. → /docs/api/embeddings#request

  • 429
    rate_limit_exceeded

    rate_limit_error

    all routes with a key

    After X-RateLimit-Reset

    The key's own requests-per-minute limit (60-6,000, default 600) is used up.

    Fix Wait X-RateLimit-Reset seconds. There is no Retry-After header. → /docs/guides/rate-limits

  • 429
    api_key_limit_exceeded

    rate_limit_error

    chat/completions, images, videos, audio/speech, audio/transcriptions, embeddings

    After 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.

    Fix 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-keys

  • 429
    upload_rate_limit_exceeded

    rate_limit_error

    audio/uploads

    Yes, with backoff

    More than 20 voice sample uploads in one hour (per account, website and API together).

    Fix Wait the Retry-After seconds; reuse an uploaded url for 24 hours instead of uploading the same clip again.

  • 429
    trial_limit

    rate_limit_error

    all generating routes

    After a top-up

    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.

    Fix Top up once to lift the trial rules, or lower max_tokens. ↗ /dashboard/billing

  • 429
    upstream_rate_limited

    rate_limit_error

    chat/completions

    Yes, with backoff

    The model provider is throttling requests.

    Fix Retry with exponential backoff, or use another model.

  • 499
    client_closed_request

    invalid_request_error

    chat/completions (stream)

    n/a

    The client closed the stream before the first chunk. Tokens generated until then are billed, the rest of the hold is refunded.

    Fix Nothing to fix; your client ended the request.

  • 500
    internal_error

    api_error

    all routes

    Yes, with backoff

    Unexpected server error.

    Fix Retry once. If it persists, write to info@railwail.com with the time of the request (and for chat the X-Railwail-Job-Id).

  • 502
    upstream_error

    api_error

    chat/completions, embeddings

    Yes, with backoff

    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.

    Fix Retry with exponential backoff.

  • 503
    model_unavailable

    api_error

    all generating routes

    Yes, with backoff

    The model cannot run right now: no verified price, provider not configured, provider out of quota, or the provider answered with a different model (never sold under the requested name). Type is invalid_request_error when the price is missing.

    Fix Choose another model; retry later. → /models

  • 504
    provider_timeout

    api_error

    embeddings

    Yes, with backoff

    The provider did not answer within 85 seconds. The run was stopped and refunded; there is no background job to poll.

    Fix Retry, or send fewer inputs per request.

38 of 38 codes

Retry policy

Retry with backoff

429 upstream_rate_limited, 502 upstream_error, 503 model_unavailable (or switch model), 500 internal_error once. Exponential backoff with jitter.

Wait, then retry

429 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 limits

Top up first

402 insufficient_credits and 429 trial_limit (trial accounts: runs over 2 credits, 5 runs per 24 h, 3 failures in a row). Billing

Never retry unchanged

400, 401, 403, 404, 413 and 402 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);

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": {…}} without data: [DONE]. The OpenAI SDKs raise it as APIError.
  • 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 use stream: 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 (type insufficient_credits_error) on chat, monthly_limit_exceeded (type spending_limit_error) on videos and audio, and 500 internal_error on images and embeddings.
  • GET /api/v1/models answers 500 for an invalid category or provider filter instead of 400.
  • GET /api/v1/jobs/{id} answers another account's job with 403 forbidden of type authentication_error.
  • Rate-limit headers are missing on 202 answers of images and embeddings, and on GET /models and GET /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.

railwail SDK
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)
  }
}
Error Codes — Railwail Docs