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.

POSThttps://renderhour.com/api/gateway/chat/completions
POSThttps://renderhour.com/api/gateway/v1/chat/completions

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 IDDescription
mb-auto/frontierHighest performance, tuned for agentic coding
mb-auto/freeRoutes only to free models

Request body

ParameterTypeRequiredDescription
modelstringYesModel ID (e.g. gpt-4o, anthropic/claude-3-5-sonnet, mb-auto/frontier)
messagesarrayYesArray of message objects with role and content
streambooleanNoEnable server-sent event streaming (default: false)
temperaturenumberNoSampling temperature 0–2
max_tokensintegerNoMaximum tokens to generate
toolsarrayNoFunction/tool definitions for tool use

Embeddings

Generate vector embeddings from text. Compatible with the OpenAI Embeddings API.

POSThttps://renderhour.com/api/gateway/embeddings
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.

GEThttps://renderhour.com/api/gateway/models
GEThttps://renderhour.com/api/gateway/embedding-models
GEThttps://renderhour.com/api/gateway/providers
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.

POSThttps://renderhour.com/api/gateway/v1/images/generations
POSThttps://renderhour.com/api/gateway/v1/audio/speech

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.

POSThttps://renderhour.com/api/gateway/v1/videos/generations
POSThttps://renderhour.com/api/gateway/v1/music/generations
GEThttps://renderhour.com/api/gateway/v1/videos/generations/{id}

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.

POSThttps://renderhour.com/api/gateway/v1/media/uploads
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.

Statuserror_typeDescription
400invalid_requestMalformed JSON or missing required fields
400missing_client_ipUnable to determine client IP address
401authentication_requiredMissing or invalid API key
401paid_model_auth_requiredPaid model requires authentication
401promotion_limit_reachedAnonymous free-model limit reached — sign up to continue
429rate_limit_exceededFree model rate limit exceeded
503temporarily_unavailableModel 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