railwail SDK · errors
RailwailError
railwail@1.0.0 throws a RailwailError for every error answer of the API, with the HTTP status, error type and code. Timeouts, network failures and non-JSON answers throw their own errors, and two situations are not errors at all.
- Class
- RailwailError extends Error
- Fields
- status · type · code · message
- Timeout
- AbortError after 120 s
- Headers
- not exposed in 1.0.0
The railwail SDK is JavaScript/TypeScript only; there is no Python package. Python tabs on this page use the official OpenAI SDK with base_url="https://railwail.com/api/v1". OpenAI compatibility
This page documents the railwail npm SDK. cURL tabs call the same REST endpoints directly; see the REST API reference.
Import
import railwail, { RailwailError } from "railwail";Properties
| Property | Type | Value |
|---|---|---|
| status | number | HTTP status of the answer (400–503). |
| code | string | The API's error code, e.g. invalid_api_key, trial_limit. Switch on this. "unknown" when the answer had no error body. |
| type | string | The error category (list below). |
| message | string | Human-readable; for example exactly "Invalid API key". It can name the missing scope or the IP address the API saw. |
| name | "RailwailError" | Set by the constructor; instanceof RailwailError works too. |
What throws what
| Situation | You get | Notes |
|---|---|---|
| The API answers with an error (4xx/5xx JSON) | RailwailError | status, type, code from the body. |
| Your timeout runs out (default 120 s) | AbortError | err.name === "AbortError". The run continues on the server and is billed; raise railwail(key, { timeout }) for long answers. |
| No connection | TypeError | fetch failed, from Node's fetch. |
| A non-JSON answer (e.g. an HTML 502 page from the edge) | SyntaxError | From parsing the body. Retry like a 502. |
| An image run takes longer than 85 s | no error | HTTP 202: data[0].url is null; at runtime the item also has job_id for rw.job(). |
| A chat run fails after more than 25 s | no error | The status was already 200, so the result has error instead of choices. Check for it (example). |
Error types
| type | Used for |
|---|---|
| authentication_error | 401 invalid_api_key; also 403 forbidden on jobs/{id} |
| permission_error | 403 insufficient_scope, ip_not_allowed |
| invalid_request_error | 400 and 404 codes, 499; 503 model_unavailable when a model has no price |
| insufficient_credits_error | 402 insufficient_credits, spending_limit_reached |
| spending_limit_error | 402 monthly_limit_exceeded (videos, audio) |
| rate_limit_error | 429 rate_limit_exceeded, trial_limit, upstream_rate_limited |
| api_error | 500, 502, 503; also the SDK's own fallback (code "unknown") |
Error Handling
import railwail, { RailwailError } from "railwail";
const rw = railwail(process.env.RAILWAIL_API_KEY!);
try {
const reply = await rw.run("gpt-4o-mini", "Hello!", { max_tokens: 300 });
console.log(reply);
} catch (err) {
if (!(err instanceof RailwailError)) throw err; // AbortError, TypeError, SyntaxError: see above
// Switch on the code, not on the status: 402 and 429 each have several causes.
switch (err.code) {
case "invalid_api_key":
console.error("Check RAILWAIL_API_KEY");
break;
case "insufficient_scope":
case "ip_not_allowed":
console.error(err.message); // names the missing scope or the IP address the API saw
break;
case "insufficient_credits":
case "trial_limit":
console.error("Top up: https://railwail.com/dashboard/billing");
break;
case "spending_limit_reached":
console.error("Monthly limit: https://railwail.com/dashboard/settings/spend-limits");
break;
case "rate_limit_exceeded":
case "upstream_rate_limited":
console.error("Slow down and retry");
break;
case "model_not_found":
case "model_not_supported":
case "model_unavailable":
console.error("Pick another model: https://railwail.com/models");
break;
default:
console.error(err.status, err.type, err.code, err.message);
}
}Common Error Codes
The codes the SDK's methods can meet (chat, image, embed, models, job). Each links to cause, fix and retry advice in the full reference.
- 400invalid_json
Send a JSON object with Content-Type: application/json.
No: fix first - 400invalid_request
Fix the field named in
param. One request per completion instead of n > 1; tools / tool_choice instead of functions.No: fix first - 400validation_failed
Remove or correct the fields listed in
details.No: fix first - 400provider_rejected_input
Split long texts or send fewer inputs per request. The run was refunded.
No: fix first - 400invalid_category
Use one of the values the message lists, or leave the filter out.
No: fix first - 400model_not_supported
Use a chat model from OpenAI, Anthropic, Google or DeepSeek, or run the model on its model page.
No: fix first - 400provider_rejected_request
Read the message, shorten the input or adjust the parameters.
No: fix first - 401invalid_api_key
Send Authorization: Bearer rw_live_… with an active key.
No: fix first - 402insufficient_credits
Top up your balance, or lower max_tokens.
After a top-up - 402spending_limit_reached
Raise or remove the limit under Spend limits, or wait for the next month.
No: fix first - 403insufficient_scope
Create a new key with that scope or "all" (a key's scopes cannot be edited after creation).
No: fix first - 403ip_not_allowed
Add the address (or its CIDR block) to a new or rotated key, or call from an allowed host.
No: fix first - 403forbidden
Poll only jobs created with a key of your account.
No: fix first - 404model_not_found
Use a slug from the model pages or GET /api/v1/models.
No: fix first - 404job_not_found
Use the id from X-Railwail-Job-Id, the part after chatcmpl-, or job_id in a 202 answer.
No: fix first - 413too_many_embedding_values
Send fewer inputs per request, or ask for fewer dimensions. Nothing was charged.
No: fix first - 429rate_limit_exceeded
Wait X-RateLimit-Reset seconds. There is no Retry-After header.
After X-RateLimit-Reset - 429api_key_limit_exceeded
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.After X-Key-Limit-Reset - 429trial_limit
Top up once to lift the trial rules, or lower max_tokens.
After a top-up - 429upstream_rate_limited
Retry with exponential backoff, or use another model.
Yes, with backoff - 500internal_error
Retry once. If it persists, write to info@railwail.com with the time of the request (and for chat the X-Railwail-Job-Id).
Yes, with backoff - 502upstream_error
Retry with exponential backoff.
Yes, with backoff - 503model_unavailable
Choose another model; retry later.
Yes, with backoff - 504provider_timeout
Retry, or send fewer inputs per request.
Yes, with backoff
Every code links to its cause, fix and retry advice in the full reference.
Retrying
1.0.0 has no automatic retries. Retry only the codes a retry can fix; everything else fails again. For the exact wait of rate_limit_exceeded you need the X-RateLimit-Reset header, which the SDK does not expose: the fetch-based helper reads it.
import railwail, { RailwailError } from "railwail";
const RETRYABLE = new Set([
"rate_limit_exceeded",
"upstream_rate_limited",
"upstream_error",
"model_unavailable",
"internal_error",
]);
async function withRetry<T>(fn: () => Promise<T>, tries = 4): Promise<T> {
for (let attempt = 1; ; attempt++) {
try {
return await fn();
} catch (err) {
if (!(err instanceof RailwailError) || !RETRYABLE.has(err.code) || attempt >= tries) throw err;
// RailwailError carries no headers, so back off exponentially with jitter.
await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 500 + Math.random() * 250));
}
}
}
const rw = railwail(process.env.RAILWAIL_API_KEY!);
const res = await withRetry(() =>
rw.chat("gpt-4o-mini", [{ role: "user", content: "Hello" }], { max_tokens: 300 }),
);
console.log(res.choices[0].message.content);