API docs

Base URL https://api.keelrouter.com. One key for every model; each model page lists its prices and parameters.

Authentication

Create a key in the console and send it on every request as Authorization: Bearer YOUR_KEY. x-api-key works too. Keys start with sk_ and are shown once.

Language models

POST /v1/chat/completions takes the OpenAI chat format, including stream: true. Claude models also answer on POST /v1/messages in the Anthropic format, so the official SDKs work with only the base URL and key changed.

curl https://api.keelrouter.com/v1/chat/completions \
  -H "Authorization: Bearer $KEELROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "gpt-6.1-sol", "messages": [{"role": "user", "content": "Hello"}]}'

Every response has an x-request-id header; non-streaming responses also carry x-keelrouter-cost in USD. Before a call we hold an estimate from your balance (from the request size and max_tokens) and settle at the real token count when it ends.

Images and video

Image and video models are async. POST /v1/images/generations or POST /v1/video/generations with model and the model's parameters returns a task. The price is fixed when the task starts, held from your balance, and released if the task fails.

{ "code": 200, "msg": "success", "data": { "taskId": "…", "state": "pending" } }

Poll the same path with ?task_id= every 5 to 10 seconds. state is pending, processing, completed or failed. Completed tasks carry resultUrls; files are deleted after 30 days.

{
  "code": 200, "msg": "success",
  "data": {
    "taskId": "…", "model": "seedance-2.0", "state": "completed",
    "resultUrls": ["https://…/results/…/0.mp4"],
    "createTime": 1792800000000, "completeTime": 1792800090000, "costTime": 90000,
    "credits": 0.51
  }
}

Bad parameters are rejected right away with HTTP 400 and nothing is held.

Callbacks

Pass callback_url (https) when you create a task. When it ends we POST the same body as a poll response, retrying up to 3 times and again for an hour if your endpoint is down.

Each callback is signed: X-KeelRouter-Signature: t=<seconds>,v1=<hex>, where v1 is HMAC-SHA256 of t + "." + body keyed with the SHA-256 hex of the API key that created the task. Checking it is optional.

const { createHash, createHmac, timingSafeEqual } = require('crypto');

function verify(body, header, apiKey) {
  const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const key = createHash('sha256').update(apiKey).digest('hex');
  const mac = createHmac('sha256', key).update(`${t}.${body}`).digest('hex');
  return timingSafeEqual(Buffer.from(mac), Buffer.from(v1));
}

Records and balance

GET /v1/records/{id} returns the exact amount charged for a request (the x-request-id). GET /v1/balance returns your available, held and total balance in USD. GET /v1/models lists every model and its endpoint, no key needed.

Errors

Errors use { "code": <status>, "msg": "…" }: 400 bad request, 401 missing or invalid key, 402 not enough balance, 404 unknown model or task, 429 rate limited, 502 or 503 upstream trouble. Failed async tasks report a failCode:

failCodeMeaningRetry
CONTENT_MODERATIONThe model refused the content.no
INVALID_INPUTA parameter or input file was not accepted.no
INSUFFICIENT_BALANCENot enough balance.no
UPSTREAM_BUSYEvery route was busy or rate-limited.yes
UPSTREAM_FAILEDThe generation failed upstream.yes
TIMEOUTThe task ran past its time limit.yes
INTERNAL_ERRORSomething failed on our side.yes

Failed and refused calls are never charged.