Getting started
Authentication
Every API request sends a key as a Bearer token. Keys start with rw_live_, belong to your account and can be limited by scope, IP address, request rate, daily and monthly credits, and expiry date.
- Header
- Authorization: Bearer rw_live_…
- Format
- rw_live_ + 64 hex characters
- New keys
- scopes read + chat · 600/min
- Per account
- up to 20 active keys
API Keys
Create keys under API keys. The full key is shown once, when you create it; Railwail stores only a hash, so copy it right away. There is one kind of key: every request is real and billed (there are no test keys). For testing, use a separate key with narrow scopes, a low max_tokens and a monthly cap under Spend limits.
Keep your key secret
Never put a key in client-side code, a public repository or a frontend bundle: anyone with it spends your balance. Read it from an environment variable on your server. If a key leaks, revoke or rotate it at once.Store the key
All examples in these docs read RAILWAIL_API_KEY from the environment:
export RAILWAIL_API_KEY="rw_live_..."$env:RAILWAIL_API_KEY = "rw_live_..."RAILWAIL_API_KEY=rw_live_...Test your key (free)
curl -s "https://railwail.com/api/v1/models?limit=1" \
-H "Authorization: Bearer $RAILWAIL_API_KEY"| Answer | Meaning |
|---|---|
| 200 + one model | The key works and has the read scope (every new key does). No credits used. |
| 401 invalid_api_key | Missing, mistyped, revoked or expired key. The message is exactly "Invalid API key". |
| 403 ip_not_allowed | The key has an IP allowlist; the message names the address the API saw. |
| 403 insufficient_scope | The key was created without read. It can still work for its other scopes. |
Scopes
A key only reaches the routes of its scopes. New keys get read and chat: for images, video, audio or embeddings tick those scopes (or all) when you create the key, otherwise the answer is 403 insufficient_scope with the message "This API key lacks the 'images' scope". Scopes cannot be changed on an existing key.
| Scope | Routes (under /api/v1) | New key |
|---|---|---|
| read | GET /models and /models/{slug} when you send a key | included |
| chat | POST /chat/completions | included |
| images | POST /images/generations | tick it |
| video | POST /videos/generations | tick it |
| audio | POST /audio/speech, POST /audio/transcriptions, POST /audio/uploads | tick it |
| embeddings | POST /embeddings | tick it |
| jobs | GET /jobs/{id} for any job (without it: jobs of a kind the key may create) | tick it |
| fine_tuning | POST/GET /fine_tuning/jobs, /fine_tuning/jobs/{id} and its cancel, events and checkpoints | tick it |
| all | every route above | tick it |
IP allowlist
- Optional, up to 50 entries: single IPv4 or IPv6 addresses or CIDR blocks (
203.0.113.7,198.51.100.0/24,2001:db8::/48). - An empty list allows every address. A key with a list refuses everything else, and also a request whose address cannot be determined.
- The address checked is the one your request comes from as Cloudflare sees it. If your server connects over IPv6, list its IPv6 address or range too; the 403
ip_not_allowedmessage tells you which address arrived.
Rate limit, credit limits and expiry
Requests per minute
Daily and monthly credits
Limits & alerts on the API keys page; counted per UTC day and calendar month. Over the limit a run answers 429 api_key_limit_exceeded with X-Key-Limit-Reset and nothing is charged. Fine-tuning jobs do not count against it (yet). Credit limitsExpiry
invalid_api_key.Revoke
Rotate
Using the SDK
Pass the key when you create the client of the railwail npm SDK; it sends it with every request.
import railwail from "railwail";
// Recommended: read the key from the environment (the SDK does not do it for you).
const rw = railwail(process.env.RAILWAIL_API_KEY!);import railwail from "railwail";
// With options: a longer timeout for long answers (default 120 s).
const rw = railwail(process.env.RAILWAIL_API_KEY!, { timeout: 300_000 });Using the REST API
For direct HTTP requests, send the key as a Bearer token in the Authorization header:
curl https://railwail.com/api/v1/chat/completions \
-H "Authorization: Bearer $RAILWAIL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "gpt-4o-mini", "max_tokens": 300, "messages": [{"role": "user", "content": "Hello!"}]}'import os
import requests
response = requests.post(
"https://railwail.com/api/v1/chat/completions",
headers={"Authorization": f"Bearer {os.environ['RAILWAIL_API_KEY']}"},
json={
"model": "gpt-4o-mini",
"max_tokens": 300,
"messages": [{"role": "user", "content": "Hello!"}],
},
timeout=120,
)
print(response.json())const response = 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({
model: "gpt-4o-mini",
max_tokens: 300,
messages: [{ role: "user", content: "Hello!" }],
}),
});
const data = await response.json();
console.log(data.choices?.[0]?.message.content ?? data.error);With the OpenAI SDK
The chat, image and audio endpoints follow OpenAI's API, so the official SDKs work with the Railwail base URL and key. What works and what does not
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["RAILWAIL_API_KEY"],
base_url="https://railwail.com/api/v1",
)import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.RAILWAIL_API_KEY,
baseURL: "https://railwail.com/api/v1",
});Without a key
Two things work without any key and cost nothing:
- The model catalog:
GET https://railwail.com/api/v1/modelsand/models/{slug}(with a key they needreadand count toward its limit). Models API - The read-only tools of the MCP server at
https://railwail.com/api/mcp(catalog, prices, docs; 30 requests per minute per IP). MCP server