Gateway API
A single OpenAI-compatible endpoint that routes to 500+ models across 60+ providers. Drop it in wherever you already use the OpenAI SDK — no other changes required.
Base URL
https://renderhour.com/api/gateway
Authentication
All requests must include your API key as a Bearer token in the Authorization header. You can find your key on the Profile page after signing in.
curl https://renderhour.com/api/gateway/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "gpt-4o-mini", "messages": [{"role": "user", "content": "Hello"}]}'Free models can be used without authentication, subject to rate limits.
Chat Completions
Fully compatible with the OpenAI Chat Completions API. Both streaming and non-streaming modes are supported.
Example — non-streaming
curl https://renderhour.com/api/gateway/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-3-5-sonnet",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is the capital of France?"}
]
}'Example — streaming
curl https://renderhour.com/api/gateway/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-3-5-sonnet",
"stream": true,
"messages": [{"role": "user", "content": "Tell me a joke"}]
}'Example — OpenAI SDK
import OpenAI from "openai";
const client = new OpenAI({
apiKey: "YOUR_API_KEY",
baseURL: "https://renderhour.com/api/gateway",
});
const response = await client.chat.completions.create({
model: "anthropic/claude-3-5-sonnet",
messages: [{ role: "user", content: "Hello!" }],
});
console.log(response.choices[0].message.content);Auto Model
Set model to one of the Auto Model IDs to let the Gateway choose the best model for each request.
| Model ID | Description |
|---|---|
| mb-auto/frontier | Highest performance, tuned for agentic coding |
| mb-auto/free | Routes only to free models |
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| model | string | Yes | Model ID (e.g. gpt-4o, anthropic/claude-3-5-sonnet, mb-auto/frontier) |
| messages | array | Yes | Array of message objects with role and content |
| stream | boolean | No | Enable server-sent event streaming (default: false) |
| temperature | number | No | Sampling temperature 0–2 |
| max_tokens | integer | No | Maximum tokens to generate |
| tools | array | No | Function/tool definitions for tool use |
Embeddings
Generate vector embeddings from text. Compatible with the OpenAI Embeddings API.
curl https://renderhour.com/api/gateway/embeddings \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "text-embedding-3-small",
"input": "The quick brown fox"
}'import OpenAI from "openai";
const client = new OpenAI({
apiKey: "YOUR_API_KEY",
baseURL: "https://renderhour.com/api/gateway",
});
const result = await client.embeddings.create({
model: "text-embedding-3-small",
input: "The quick brown fox",
});
console.log(result.data[0].embedding);List Models
Retrieve the list of models available through the Gateway. The response follows the OpenAI models format.
curl https://renderhour.com/api/gateway/models \
-H "Authorization: Bearer YOUR_API_KEY"{
"data": [
{
"id": "anthropic/claude-3-5-sonnet",
"name": "Claude 3.5 Sonnet",
"context_length": 200000,
"pricing": { "prompt": "0.000003", "completion": "0.000015" }
},
...
]
}Media generation
Images and speech answer inline, in the OpenAI shape. Video and music are jobs, because a render holds the connection for minutes. Every model is priced per run at a rate you can read before you send anything — the price is fixed at intake, and a failed run is never charged.
Example — one image
curl https://renderhour.com/api/gateway/v1/images/generations \
-H "Authorization: Bearer $RENDERHOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "kie/google/nano-banana", "prompt": "A lighthouse on a basalt cliff at dusk"}'What you may not set, and why
Every price here is quoted at one basis — one image at a stated resolution, one second of 720p video — and the vendors bill a multiple above it. So the parameters that would move the vendor’s meter without moving your price are refused (size, resolution, quality, n and their synonyms), and where a vendor requires one, the gateway sends the value the price was quoted at rather than asking you for one it would reject. A refusal names the tier: resolution is fixed at 720p.
Length is the exception, because it changes your price too: pass seconds on a per-second model and you are quoted and charged for exactly that. On a model priced per clip, seconds is refused — the clip has one length and one price.
Client libraries
There is no RenderHour SDK, and that is a decision rather than an omission. The image and speech routes are the OpenAI shape, so the OpenAI SDK you already have speaks them — point baseURL here. The job routes are twenty lines of polling, printed in full on every model page in the catalogue, in curl, Python and JavaScript. A package to wrap that would be a dependency, a version to keep current and a second place for the contract to drift; if enough people ask, that trade changes and this paragraph goes away.
Video and music jobs
A submission answers 202 with a job id and the price it will cost. Poll the same path, or supply webhook_url and be told — the webhook carries the body the status endpoint answers with, signed, and retried on a fixed backoff.
Example — submit and poll
const headers = {
Authorization: `Bearer ${process.env.RENDERHOUR_API_KEY}`,
"Content-Type": "application/json",
};
const submit = await fetch("https://renderhour.com/api/gateway/v1/videos/generations", {
method: "POST",
headers,
body: JSON.stringify({
model: "kie/wan/2-6-text-to-video",
prompt: "Slow aerial shot over a foggy coastline at sunrise",
seconds: 5,
}),
});
const { id } = await submit.json(); // 202 Accepted — the render is queued
while (true) {
const job = await (await fetch(`https://renderhour.com/api/gateway/v1/videos/generations/${id}`, { headers })).json();
if (job.status === "succeeded") {
console.log(job.data[0].url);
break;
}
if (job.status === "failed" || job.status === "cancelled") {
throw new Error(job.error ?? job.status);
}
await new Promise(r => setTimeout(r, 5000));
}Statuses are queued, running, succeeded, failed and cancelled. Send an Idempotency-Key header: without one, a retried POST after a dropped response takes a second hold for the same render.
Input files and results
A model that edits or animates something needs that something to be reachable. Pass a URL you host, or ask for somewhere to put one.
curl -X POST https://renderhour.com/api/gateway/v1/media/uploads \
-H "Authorization: Bearer $RENDERHOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filename": "reference.png", "content_type": "image/png"}'
# → { "upload_url": …, "file_url": …, "storage_key": …,
# "upload_expires_in": 900, "file_expires_in": 21600,
# "max_bytes": …, "retention_days": 30 }
curl -X PUT "$UPLOAD_URL" --upload-file reference.png -H "Content-Type: image/png"
# Then pass file_url as the model's own input field — image_url, video_url, …The upload link lives 15 minutes (upload_expires_in) and the read link six hours (file_expires_in) — the read is used by the vendor, at a time we do not choose, and a queued job may wait an hour before it starts. Inputs are kept 30 days and results 7; a result comes back as an expiring signed link, so download what you mean to keep. Links to video platforms (YouTube, TikTok, Instagram and the rest) are refused as inputs, by host.
Media limits and history
Two rules, both per account, both answering 429 with Retry-After: five jobs open at once, and 30 requests a minute or 600 an hour. Refusals count towards the rate limit — the caller who most needs slowing is the one retrying a request we keep rejecting. Inline runs hold nothing in the queue and are exempt from the concurrency rule.
Every run, refusals included, is listed at GET https://renderhour.com/api/gateway/v1/media/requests with its price, status and error, and in the cabinet at /media-runs. Media spends the same balance as text, and both appear in one usage history.
Errors
The Gateway uses standard HTTP status codes. Error responses include an error field and an error_type machine-readable code.
| Status | error_type | Description |
|---|---|---|
| 400 | invalid_request | Malformed JSON or missing required fields |
| 400 | missing_client_ip | Unable to determine client IP address |
| 401 | authentication_required | Missing or invalid API key |
| 401 | paid_model_auth_required | Paid model requires authentication |
| 401 | promotion_limit_reached | Anonymous free-model limit reached — sign up to continue |
| 429 | rate_limit_exceeded | Free model rate limit exceeded |
| 503 | temporarily_unavailable | Model or provider is temporarily unavailable |
{
"error": {
"code": "PAID_MODEL_AUTH_REQUIRED",
"message": "You need to sign in to use this model."
},
"error_type": "paid_model_auth_required"
}Ready to start?
Create a free account and get your API key in 2 minutes.
Get started free