AI API documentation
Choose one route: keep using your OpenAI, Claude, or Gemini SDK for text and chat, or use the task API for images and video. Quick Start prepares your API key and the matching request for a live model.
Paste it into a coding assistant. It will choose the route for your use case and verify current models and fields.
Which API do you want to call?
Choose the option that matches your existing project or generation goal. You do not need to learn all four APIs.
POST /v1/chat/completionsText and chatI use the Claude SDKKeep using the Messages API.POST /v1/messagesText and chatI use the Gemini SDKKeep using GenerateContent.POST ...:generateContentImages and videoI want to generate images or videoCreate an asynchronous task, then query its result.POST /v1/tasksQuickstart
Create a key, choose a model, then run the example for your use case. You do not need to read this entire page before making the first request.
1. Get your API key
Registration creates your first key automatically. Open Quick Start to copy it and a working request. Save MODEL_API_KEY as a server environment variable; keep it out of client code and public repositories. Get an API key →
API_BASE=https://newrouters.com
MODEL_API_KEY=sk_••••••••2. Choose a currently available model
Choose text and chat models from GET /v1/models, or image and video models from GET /v1/media-models. Copy the returned model field instead of typing an ID from memory.
GET /v1/modelsText and chat model IDs and protocols
GET /v1/media-modelsImage and video model IDs, fields, and prices
curl --fail-with-body "https://newrouters.com/v1/models"
curl --fail-with-body "https://newrouters.com/v1/media-models"const [llmResponse, mediaResponse] = await Promise.all([
fetch('https://newrouters.com/v1/models'),
fetch('https://newrouters.com/v1/media-models')
]);
if (!llmResponse.ok) throw new Error(await llmResponse.text());
if (!mediaResponse.ok) throw new Error(await mediaResponse.text());
const llmModels = await llmResponse.json();
const mediaModels = await mediaResponse.json();
console.log({ llmModels, mediaModels });import requests
llm_response = requests.get("https://newrouters.com/v1/models", timeout=30)
media_response = requests.get("https://newrouters.com/v1/media-models", timeout=30)
llm_response.raise_for_status()
media_response.raise_for_status()
llm_models = llm_response.json()
media_models = media_response.json()
print({"llm_models": llm_models, "media_models": media_models})3. Run the example for your use case
Choose one compatible protocol for text and chat, or create and query a task for images and video.
Keep using the SDK and request format you already know.
Configure this base URL and API key in your OpenAI, Claude, or Gemini integration, then choose a live model for that protocol. Request bodies, streaming, usage, and errors stay protocol-specific.
POST /v1/chat/completions↓ClaudePOST /v1/messages↓GeminiPOST /v1beta/models/{model}:generateContent↓Choose the protocol used by your existing SDK, set its model environment variable, and copy one complete non-streaming request.
Currently available models
These models have a published price and an available upstream route. Open a model page for live pricing, limits, and native request examples.
OpenAI Chat Completions
Use Bearer authentication. Chat Completions is the first request below; Responses uses the same key and base URL with its native body.
- Authentication
Authorization: Bearer $MODEL_API_KEY- Model environment variable
OPENAI_MODEL_ID
POST /v1/chat/completionsPOST /v1/responsesGET /v1/models
# Bash
curl --request POST "https://newrouters.com/v1/chat/completions" \
--header "Authorization: Bearer $MODEL_API_KEY" \
--header "Content-Type: application/json" \
--data @- <<JSON
{
"model": "$OPENAI_MODEL_ID",
"messages": [{"role": "user", "content": "Explain idempotent Webhook handling in three steps."}],
"max_tokens": 512
}
JSONconst response = await fetch('https://newrouters.com/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.MODEL_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: process.env.OPENAI_MODEL_ID,
messages: [{ role: 'user', content: 'Explain idempotent Webhook handling in three steps.' }],
max_tokens: 512
})
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());import os
import requests
response = requests.post(
"https://newrouters.com/v1/chat/completions",
headers={"Authorization": f"Bearer {os.environ['MODEL_API_KEY']}"},
json={
"model": os.environ["OPENAI_MODEL_ID"],
"messages": [{"role": "user", "content": "Explain idempotent Webhook handling in three steps."}],
"max_tokens": 512,
},
timeout=60,
)
response.raise_for_status()
print(response.json())Claude Messages
Use x-api-key and an anthropic-version header. Count Tokens and authenticated model discovery use the same account key.
- Authentication
x-api-key: $MODEL_API_KEY- Model environment variable
CLAUDE_MODEL_ID
GET /v1/modelsPOST /v1/messagesPOST /v1/messages/count_tokens
# Bash
curl --request POST "https://newrouters.com/v1/messages" \
--header "x-api-key: $MODEL_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "Content-Type: application/json" \
--data @- <<JSON
{
"model": "$CLAUDE_MODEL_ID",
"max_tokens": 512,
"messages": [{"role": "user", "content": "Explain idempotent Webhook handling in three steps."}]
}
JSONconst response = await fetch('https://newrouters.com/v1/messages', {
method: 'POST',
headers: {
'x-api-key': process.env.MODEL_API_KEY,
'anthropic-version': '2023-06-01',
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: process.env.CLAUDE_MODEL_ID,
max_tokens: 512,
messages: [{ role: 'user', content: 'Explain idempotent Webhook handling in three steps.' }]
})
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());import os
import requests
response = requests.post(
"https://newrouters.com/v1/messages",
headers={
"x-api-key": os.environ["MODEL_API_KEY"],
"anthropic-version": "2023-06-01",
},
json={
"model": os.environ["CLAUDE_MODEL_ID"],
"max_tokens": 512,
"messages": [{"role": "user", "content": "Explain idempotent Webhook handling in three steps."}],
},
timeout=60,
)
response.raise_for_status()
print(response.json())Gemini GenerateContent
Use x-goog-api-key and place the selected Gemini model ID in the native request path. Interactions remains a separate native endpoint.
- Authentication
x-goog-api-key: $MODEL_API_KEY- Model environment variable
GEMINI_MODEL_ID
POST /v1beta/models/{model}:generateContentPOST /v1beta/models/{model}:streamGenerateContent?alt=ssePOST /v1/interactionsPOST /v1beta/interactions
curl --request POST "https://newrouters.com/v1beta/models/${GEMINI_MODEL_ID}:generateContent" \
--header "x-goog-api-key: $MODEL_API_KEY" \
--header "Content-Type: application/json" \
--data '{"contents":[{"role":"user","parts":[{"text":"Explain idempotent Webhook handling in three steps."}]}]}'const path = '/v1beta/models/' + process.env.GEMINI_MODEL_ID + ':generateContent';
const response = await fetch('https://newrouters.com' + path, {
method: 'POST',
headers: {
'x-goog-api-key': process.env.MODEL_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
contents: [{ role: 'user', parts: [{ text: 'Explain idempotent Webhook handling in three steps.' }] }]
})
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());import os
import requests
model = os.environ["GEMINI_MODEL_ID"]
response = requests.post(
f"https://newrouters.com/v1beta/models/{model}:generateContent",
headers={"x-goog-api-key": os.environ["MODEL_API_KEY"]},
json={
"contents": [{
"role": "user",
"parts": [{"text": "Explain idempotent Webhook handling in three steps."}],
}]
},
timeout=60,
)
response.raise_for_status()
print(response.json())Streaming stays protocol-native
OpenAI and Claude stream when the JSON body contains stream: true. Gemini uses streamGenerateContent with alt=sse, or stream: true for Interactions.
# Bash
curl "https://newrouters.com/v1/chat/completions" \
-H "Authorization: Bearer $MODEL_API_KEY" \
-H "Content-Type: application/json" \
--data @- <<JSON
{"model":"$OPENAI_MODEL_ID","messages":[{"role":"user","content":"Stream a short answer."}],"stream":true}
JSON# Bash
curl "https://newrouters.com/v1/messages" \
-H "x-api-key: $MODEL_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
--data @- <<JSON
{"model":"$CLAUDE_MODEL_ID","max_tokens":256,"messages":[{"role":"user","content":"Stream a short answer."}],"stream":true}
JSON# Bash
curl "https://newrouters.com/v1beta/models/${GEMINI_MODEL_ID}:streamGenerateContent?alt=sse" \
--header "x-goog-api-key: $MODEL_API_KEY" \
--header "Content-Type: application/json" \
--data '{"contents":[{"role":"user","parts":[{"text":"Stream a short answer."}]}]}'Read native usage and errors
Successful responses preserve the protocol usage object. Request failures use the OpenAI, Claude, or Gemini native error envelope, so existing client handling can remain protocol-specific.
Create a task, then query the generated result.
The recommended image and video task API returns a task ID first; query that task until it succeeds, then read the result file URL. An optional synchronous image route appears under Create a task.
/v1/tasksHTTP 202Create a task
Send a model ID and its input object. Add callback_url only when you want a success or failure notification. Every POST creates and charges a new task, so do not automatically retry a create request whose outcome is uncertain.
Creating a task may incur charges
Check the model, input fields, and current price in GET /v1/media-models first. Both asynchronous and synchronous image creation can create and charge a task.
# Bash
curl --request POST "https://newrouters.com/v1/tasks" \
--header "Authorization: Bearer $MODEL_API_KEY" \
--header "Content-Type: application/json" \
--data @- <<'JSON'
{
"model": "gpt-image-2",
"input": {
"prompt": "A cobalt-blue mechanical bird on a white plinth, clean product photography",
"aspect_ratio": "1:1",
"resolution": "1k",
"quality": "low",
"output_format": "png"
}
}
JSONconst response = await fetch('https://newrouters.com/v1/tasks', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.MODEL_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
"model": "gpt-image-2",
"input": {
"prompt": "A cobalt-blue mechanical bird on a white plinth, clean product photography",
"aspect_ratio": "1:1",
"resolution": "1k",
"quality": "low",
"output_format": "png"
}
})
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());import os
import requests
response = requests.post(
"https://newrouters.com/v1/tasks",
headers={"Authorization": f"Bearer {os.environ['MODEL_API_KEY']}"},
json={
"model": "gpt-image-2",
"input": {
"prompt": "A cobalt-blue mechanical bird on a white plinth, clean product photography",
"aspect_ratio": "1:1",
"resolution": "1k",
"quality": "low",
"output_format": "png"
}
},
timeout=60,
)
response.raise_for_status()
print(response.json())| Field | Required | Meaning |
|---|---|---|
model | Yes | Public model ID from the enabled media catalog. |
input | Yes | The model-specific input object. Unknown fields are rejected. |
callback_url | No | Optional public HTTP(S) endpoint that receives a success or failure notification. Do not use credentials, localhost, or private/reserved addresses. |
Task creation is not safe to retry automatically
Every POST /v1/tasks call can create and charge a new task. GET requests may use backoff.
{
"id": "task_01M19Q7KDPY0KSK6PQ3W2X9M7A",
"status": "pending",
"type": "image",
"model": "gpt-image-2",
"request": {
"prompt": "A cobalt-blue mechanical bird on a white plinth, clean product photography",
"aspect_ratio": "1:1",
"resolution": "1k",
"quality": "low",
"output_format": "png"
},
"result": null,
"error": null,
"estimated_points": "2.00",
"charged_points": "2.00",
"refunded_points": "0.00",
"created_at": "2026-08-26T08:00:00.000Z",
"started_at": null,
"updated_at": "2026-08-26T08:00:00.000Z",
"completed_at": null
}Create a synchronous task (not recommended)
For image generation and editing with one or more reference images. Use the direct API host below. Editing accepts OpenAI-style JSON image references or multipart file uploads. Each accepted POST creates a billable task and holds the connection while it runs; the asynchronous task API above is the recommended integration.
Base URL: https://sync.newrouters.com
Generate an image · POST /v1/images/generations
curl --request POST "https://sync.newrouters.com/v1/images/generations" \
--header "Authorization: Bearer $MODEL_API_KEY" \
--header "Content-Type: application/json" \
--data @- <<'JSON'
{
"model": "gpt-image-2",
"prompt": "A blue ceramic mug on a white table",
"n": 1,
"response_format": "url",
"size": "1024x1024",
"quality": "low",
"output_format": "jpeg"
}
JSONEdit images (JSON or multipart) · POST /v1/images/edits
curl --request POST "https://sync.newrouters.com/v1/images/edits" \
--header "Authorization: Bearer $MODEL_API_KEY" \
--header "Content-Type: application/json" \
--data @- <<'JSON'
{
"model": "gpt-image-2",
"prompt": "Place both reference mugs side by side on a white table",
"images": [
{ "image_url": "https://your-domain.example/reference-1.png" },
{ "image_url": "https://your-domain.example/reference-2.png" }
],
"n": 1,
"response_format": "url",
"size": "1024x1024",
"quality": "low",
"output_format": "jpeg"
}
JSON
# OpenAI-style multipart upload (default response: data[0].b64_json)
curl --request POST "https://sync.newrouters.com/v1/images/edits" \
--header "Authorization: Bearer $MODEL_API_KEY" \
--form "model=gpt-image-2" \
--form "prompt=Place both reference mugs side by side on a white table" \
--form "image[][email protected]" \
--form "image[][email protected]"{
"created": 1790166146,
"data": [
{
"url": "https://media.newrouters.com/tasks/task_01M.../outputs/0.jpeg"
}
]
}Only n=1 is supported. Editing accepts JSON images[].image_url values as public HTTP(S) URLs or PNG/JPEG/WebP Base64 data URLs, or multipart image/image[] files, up to a 50 MiB request body. The model and channel determine the image-count limit. Edits return data[0].b64_json by default; response_format=url enables the platform URL extension. Generation still defaults to data[0].url. Masks, file_id, and partial-image streaming are unavailable. size accepts auto or WxH with 2–5 digit positive dimensions and maps to the nearest supported aspect ratio and 1K/2K/4K tier; actual pixels may differ by channel. When size is supplied, resolution is ignored; size=auto is billed as 2K even with resolution=4k and does not guarantee 4K output. Direct requests wait up to 350 seconds. If the wait expires, the task keeps running: use error.details.task_id with GET https://newrouters.com/v1/tasks/{task_id} instead of submitting again.
/v1/tasks/{task_id}HTTP 200Query until a final state
Poll only while status is pending or processing. Stop after succeeded or failed, or use a Webhook instead of continuous polling.
curl "https://newrouters.com/v1/tasks/$TASK_ID" \
--header "Authorization: Bearer $MODEL_API_KEY"const response = await fetch(
'https://newrouters.com/v1/tasks/' + process.env.TASK_ID,
{ headers: { Authorization: `Bearer ${process.env.MODEL_API_KEY}` } }
);
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());import os
import requests
response = requests.get(
f"https://newrouters.com/v1/tasks/{os.environ['TASK_ID']}",
headers={"Authorization": f"Bearer {os.environ['MODEL_API_KEY']}"},
timeout=30,
)
response.raise_for_status()
print(response.json())Waiting for scheduling
Generation is running
Read result.assets
Read error.code and error.message
{
"id": "task_01M19Q7KDPY0KSK6PQ3W2X9M7A",
"status": "succeeded",
"type": "image",
"model": "gpt-image-2",
"request": {
"prompt": "A cobalt-blue mechanical bird on a white plinth, clean product photography",
"aspect_ratio": "1:1",
"resolution": "1k",
"quality": "low",
"output_format": "png"
},
"result": {
"assets": [
"https://cdn.example.com/tasks/task_01M19Q7KDPY0KSK6PQ3W2X9M7A/outputs/0.png"
]
},
"error": null,
"estimated_points": "2.00",
"charged_points": "2.00",
"refunded_points": "0.00",
"created_at": "2026-08-26T08:00:00.000Z",
"started_at": "2026-08-26T08:00:02.000Z",
"updated_at": "2026-08-26T08:00:28.000Z",
"completed_at": "2026-08-26T08:00:28.000Z"
}{
"id": "task_01M19Q7KDPY0KSK6PQ3W2X9M7A",
"status": "failed",
"type": "image",
"model": "gpt-image-2",
"request": {
"prompt": "A cobalt-blue mechanical bird on a white plinth, clean product photography",
"aspect_ratio": "1:1",
"resolution": "1k",
"quality": "low",
"output_format": "png"
},
"result": null,
"error": {
"code": "model_error",
"message": "The model could not complete the task."
},
"estimated_points": "2.00",
"charged_points": "0.00",
"refunded_points": "2.00",
"created_at": "2026-08-26T08:00:00.000Z",
"started_at": "2026-08-26T08:00:02.000Z",
"updated_at": "2026-08-26T08:00:16.000Z",
"completed_at": "2026-08-26T08:00:16.000Z"
}Response request and retention
response.request is the normalized model input after defaults, not the outer create envelope. Image processing is capped at 5 minutes and video at 30 minutes. Result files are retained for 7 days by default; the task remains afterward but result.assets may be empty.
Receive success or failure through a Webhook.
After a task succeeds or fails, the platform sends one unsigned JSON POST to a public HTTP(S) callback_url. Credentials, localhost, private/reserved addresses, and redirects are rejected; delivery times out after 10 seconds. Any 2xx response counts as delivered.
One automatic delivery
Use event_id for deduplication. If delivery fails, query the task directly; automatic retries are not performed.
{
"event_id": "evt_01M19R2W5PDCXE3QP05FHYG9VM",
"event_type": "task.succeeded",
"created_at": "2026-08-26T08:00:28.000Z",
"data": {
"task_id": "task_01M19Q7KDPY0KSK6PQ3W2X9M7A",
"status": "succeeded"
},
"error": null
}Trusted stateBecause Webhooks are unsigned, treat GET /v1/tasks/{task_id} with your API key as the trusted state when confirmation matters.
Separate task failure from request failure.
A media task that was accepted can later fail while its query still returns HTTP 200. A request that was never accepted returns a non-2xx error immediately. LLM requests keep their native protocol error envelope.
Accepted media task failure
Read status, error.code, error.message, and safe error.details from the task resource.
Media HTTP request failure
Read the top-level error object. Do not start task polling after a non-2xx create response.
{
"id": "task_01M19Q7KDPY0KSK6PQ3W2X9M7A",
"status": "failed",
"type": "image",
"model": "gpt-image-2",
"request": {
"prompt": "A cobalt-blue mechanical bird on a white plinth, clean product photography",
"aspect_ratio": "1:1",
"resolution": "1k",
"quality": "low",
"output_format": "png"
},
"result": null,
"error": {
"code": "model_error",
"message": "The model could not complete the task."
},
"estimated_points": "2.00",
"charged_points": "0.00",
"refunded_points": "2.00",
"created_at": "2026-08-26T08:00:00.000Z",
"started_at": "2026-08-26T08:00:02.000Z",
"updated_at": "2026-08-26T08:00:16.000Z",
"completed_at": "2026-08-26T08:00:16.000Z"
}{
"error": {
"code": "invalid_request",
"message": "The task input is invalid.",
"request_id": "req_01M19R8KQKBE6VFSR4Z4P8RZK6",
"details": {}
}
}Native LLM request errors
Successful responses preserve the protocol usage object. Request failures use the OpenAI, Claude, or Gemini native error envelope, so existing client handling can remain protocol-specific.
{
"error": {
"message": "A valid API key is required.",
"type": "authentication_error",
"param": null,
"code": "authentication_error"
}
}{
"type": "error",
"error": {
"type": "authentication_error",
"message": "A valid API key is required."
},
"request_id": "req_01M19R8KQKBE6VFSR4Z4P8RZK6"
}{
"error": {
"code": 401,
"message": "A valid API key is required.",
"status": "UNAUTHENTICATED",
"details": []
}
}Retry guidance
Policy and validation errors require changing the input. Media-task creation is not idempotent: every POST can create and charge a new task, so do not automatically retry an uncertain create request. Read-only media requests may use backoff. Follow the selected SDK retry behavior for LLM requests.
Check platform points.
GET /v1/balance uses Bearer authentication and returns the current points for the API key owner. The decimal string does not include points held by in-flight tasks.
/v1/balanceBearer API Keycurl "https://newrouters.com/v1/balance" \
--header "Authorization: Bearer $MODEL_API_KEY"const response = await fetch('https://newrouters.com/v1/balance', {
headers: { Authorization: `Bearer ${process.env.MODEL_API_KEY}` }
});
if (!response.ok) throw new Error(await response.text());
console.log(await response.json());import os
import requests
response = requests.get(
"https://newrouters.com/v1/balance",
headers={"Authorization": f"Bearer {os.environ['MODEL_API_KEY']}"},
timeout=30,
)
response.raise_for_status()
print(response.json()){
"points": "123.456789"
}Choose a model and copy its example.
Use model pages to compare models and read input guidance. Use the live catalog to confirm that a model is callable and to read current fields and pricing.