Point your OpenAI client at Realrouter.

The API keeps familiar OpenAI-style routes for responses, chat completions, files, and models. Replace the base URL, use your Realrouter key, and keep your client code small.

Quickstart

Install the official OpenAI SDK and set the Realrouter base URL. The dashboard will show your API key once when it is created.

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.REALROUTER_KEY,
  baseURL: "https://api.realrouter.org/v1",
});

const response = await client.responses.create({
  model: "gpt-5.4-mini",
  reasoning: { effort: "low" },
  input: "Summarize this note in Chinese.",
});

console.log(response.output_text);
Starter500 credits / month
API Keysuser-level subscription
Mobile Chatsame account balance

Endpoints

  • POST /v1/responses for the primary OpenAI-compatible responses API.
  • POST /v1/chat/completions for legacy chat completion clients.
  • GET /v1/models for available models, capabilities, and pricing metadata.
  • POST /v1/files to upload a PDF or image for use as input_file / input_image.
  • GET /v1/me to read the key and account metadata for the key you are calling with.
  • GET /v1/usage/summary?days=N to read token and spend totals for the last N days.
  • GET /v1/billing/transactions to read the billing ledger for the account.

The three routes above accept a plain sk- key, so an integration can watch its own spend without a dashboard session. Account balance is deliberately not exposed to an API key.

A machine-readable OpenAPI description of every route is served at /openapi.json, with a browsable version at /api-docs.

Parameters that do not reach the model

temperature and top_p are accepted and echoed back in the response object so OpenAI clients keep working, but they are not forwarded and have no effect on output. Use reasoning.effort to trade quality against cost instead. max_output_tokens (and max_tokens / max_completion_tokens on chat completions) are also not forwarded, but they are not inert: they set the estimated output size used to reserve balance, so an inflated value can trigger 402 insufficient_balance or request_cost_limit_exceeded on a request that would otherwise have run.

The API is stateless. previous_response_id and store are accepted and echoed, but nothing is persisted and there is no GET /v1/responses/{id} — send the conversation history with every request.

Error shape

Errors are JSON with an error object. Only error.code and error.message are always present — branch on error.code. The other keys vary by family: error.type (always "invalid_request_error") and error.param appear on request-validation and upstream failures but are absent from 401, 402 and 429, so error.type is never a safe thing to branch on. error.details appears on the quota, balance and rate-limit errors and its contents differ per code; concurrency 429s carry retry_after_seconds instead.

Tools and modalities

Built-in tools are on by default: web_search is added to every request, and image_generation is added on every model that supports it, whether or not you pass a tools array. Pass the tool explicitly to configure it — for example web_search with search_context_size. Tool types the API cannot translate return 400 with unsupported_tool.

Vision input and PDF input_file are input modalities, not tools, and both require a GPT vision model — see Model support below. PDF uploads use purpose=user_data, are limited to 25 MB per file, and each response request can include up to 50 MB of inline or referenced file content.
Tool type
Status
Notes
web_search
Supported
On by default — added to every request whether or not you pass it. Also accepts web_search_preview; pass it explicitly to set search_context_size. On GPT models, streaming emits web search in-progress and completed events on /v1/responses; claude-* models emit none.
image_generation
Supported
On by default on every model that supports it. Also accepts image_generation_call. Generated images are returned as signed URLs that expire after 7 days and cannot be re-signed — download anything you need to keep. Not available on gpt-5.3-codex-spark or any claude-* model, where it is dropped from the request rather than rejected.
function
Supported
Custom function calling. Realrouter forwards declarations and returns function calls, but your client executes tools and sends results in the next request. On claude-* models, declaring any function tool turns off incremental streaming: the turn is produced in full before the first delta is sent, so stream: true still works but stops arriving token by token.
custom
Supported
Freeform tool calling on /v1/responses, including Lark grammar constraints. The model emits custom_tool_call items whose input is raw text instead of JSON; reply with custom_tool_call_output. Not available on claude-* models.
computer
Supported
Computer use on /v1/responses. Both official declarations work: {"type":"computer"} and the preview {"type":"computer_use_preview", display_width, display_height, environment}. The model emits computer_call items carrying both action and actions; reply with computer_call_output. Vision models only, so not gpt-5.3-codex-spark or claude-*. Send screenshots at 1280px or less on the long edge — larger images come back in a rescaled coordinate space.

Function calling

Function calling is stateless. Realrouter returns function_call items or chat tool_calls; your app runs the function and sends the result back with the relevant conversation context.

const first = await client.responses.create({
  model: "gpt-5.5",
  input: "What is the weather in Toronto?",
  tools: [{
    type: "function",
    name: "get_weather",
    description: "Get current weather for a city.",
    parameters: {
      type: "object",
      properties: { city: { type: "string" } },
      required: ["city"],
      additionalProperties: false,
    },
  }],
  tool_choice: { type: "function", name: "get_weather" },
});

const call = first.output.find((item) => item.type === "function_call");
const toolResult = await getWeather(JSON.parse(call.arguments));

const final = await client.responses.create({
  model: "gpt-5.5",
  input: [
    { role: "user", content: "What is the weather in Toronto?" },
    call,
    { type: "function_call_output", call_id: call.call_id, output: JSON.stringify(toolResult) },
  ],
  tools: first.tools,
});

Structured outputs

On GPT models, Responses supports OpenAI-compatible Structured Outputs with text.format.type=json_schema. Use json_schema when you need schema adherence; json_object remains available for plain JSON mode. Claude models accept text.format.type=text only and return 400 unsupported_model_for_text_format for anything else.

const response = await client.responses.create({
  model: "gpt-5.4-mini",
  input: "Extract the answer. Return exactly pong.",
  text: {
    format: {
      type: "json_schema",
      name: "pong_answer",
      schema: {
        type: "object",
        properties: {
          answer: { type: "string" },
        },
        required: ["answer"],
        additionalProperties: false,
      },
      strict: true,
    },
  },
});

const parsed = JSON.parse(response.output_text);

PDF and file input

input_file and file_id references work on /v1/responses only. /v1/chat/completions accepts text and image_url content parts and rejects anything else with 422.

curl https://api.realrouter.org/v1/files \
  -H "Authorization: Bearer $REALROUTER_KEY" \
  -F purpose=user_data \
  -F file=@document.pdf
const response = await client.responses.create({
  model: "gpt-5.4-mini",
  input: [{
    role: "user",
    content: [
      { type: "input_file", file_id: "file-..." },
      { type: "input_text", text: "Summarize this PDF." },
    ],
  }],
});

Image generation

const response = await client.responses.create({
  model: "gpt-5.4-mini",
  input: "Generate a small product icon for a code API gateway.",
  tools: [{ type: "image_generation" }],
});

Model support

Claude models reach the upstream through a different path than GPT models and support a narrower surface. Unsupported tool types return 400 unsupported_tool — including custom and computer on the models below that show No. image_generation is the one exception: on a model that lacks it, it is quietly dropped from the request instead of rejected.

Capability
GPT vision models
gpt-5.3-codex-spark
claude-* models
web_search
Yes
Yes
Yes
function calling
Yes
Yes
Yes
custom (freeform) tools
Yes
Yes
No
image_generation
Yes
No
No
computer use
Yes
No
No
image input
Yes
No
No
PDF input_file
Yes
No
No
json_schema output
Yes
Yes
No
web search streaming events
Yes
Yes
No

gpt-5.6-sol / terra / luna, gpt-5.5, gpt-5.4, gpt-5.4-mini

Claude models reject image and file input with 400 unsupported_model_for_media_input, and non-text output formats with 400 unsupported_model_for_text_format. gpt-5.3-codex-spark rejects PDF input_file, but does not reject image input — it simply has no vision, so images sent to it are not understood rather than refused. Declaring a function tool on a Claude model also turns off incremental streaming: the response arrives in fewer, larger chunks.

file_search, code_interpreter and mcp are not supported on any model and return 400 unsupported_tool.

Limits and errors

Every error carries error.code and error.message; the codes below also carry an error.details object whose contents depend on the code. Handle these two families explicitly — they are the ones your integration will actually hit.

Rate limits by plan

Your plan sets two published ceilings. Requests per minute is a token bucket that refills continuously, so an idle client can burst a full minute’s worth and a steady one is paced smoothly. Concurrency is how many requests may be in flight at once.

Plan
Requests / min
Concurrent requests
Starter
20
1
Basic
60
2
Plus
200
4

429 — slow down

  • plan_rate_limit_exceeded — you went over your plan’s requests-per-minute ceiling. details carries requests_per_minute and the response carries Retry-After.
  • quota_exhausted — your own 5h fair-share window is spent. Retry-After and x-ratelimit-* headers are on the response; details carries retry_after_unix.
  • upstream_rate_limited — the shared subscription backend is saturated. Retry with backoff; this is not your quota. Concurrency limits surface as 429 too, carrying retry_after_seconds rather than details.

Rate-limit headers accompany successful responses too: x-ratelimit-limit-5h, x-ratelimit-remaining-5h, x-ratelimit-reset-5h and the matching 7d triple. Read them to pace yourself before you are throttled.

402 — balance problem

  • insufficient_balance — your remaining balance is below the estimated cost of this request. details carries balance_usd and required_usd.
  • request_cost_limit_exceeded — a single request may not cost more than one fifth of your plan’s included usage. Shorten the input, or lower max_output_tokens, which is what sizes the estimate. reasoning.effort does not affect this check.
  • subscription_inactive — no active plan on the account.
  • subscription_period_expired — the billing period has ended.

File and image retention

Uploaded files and generated images are deleted 7 days after they are created. A file_id that has expired returns 400 invalid_file_id, which is the same error a bogus id returns. Signed image URLs carry that deadline in the URL and cannot be re-signed — download and re-host any image you need to keep.

Reading your own usage

A plain sk- key can call GET /v1/usage/summary?days=30 for token and spend totals in USD, and GET /v1/me for key metadata. Account balance is deliberately not readable with an API key — sign in to the dashboard for that.