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.
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.
Image and video requests do not return the final file immediately. POST /v1/tasks returns a task ID; query that task until it succeeds, then read the result file URL.
/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. Only POST /v1/tasks starts a generation 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
}/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.
GET /v1/tasks?status=failed&failure_reason=content_filtered&limit=20GET /v1/tasks supports cursor pagination plus status, failure_reason, type, model, task_id, and time filters. limit accepts 1–100 and defaults to 20.
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.