Platform API
Read the catalog, prices, your balance and your usage from code.
This is everything except sending a prompt: which models exist, what each provider charges, how much you have spent, and which keys you hold. It all answers JSON on the same api host you send completions to.
The catalog and price endpoints are public and need no credential at all. Anything about your own account needs one, and there are two kinds.
They are not interchangeable. An API key cannot read your balance, and an access token cannot send a completion.
| Credential | Looks like | Opens |
|---|---|---|
| API key | sk-... | Inference, the model list, and that one key's usage counter. |
| Access token | 29 to 32 characters, no prefix | Your account: balance, key management, usage logs. |
Both travel in the same header, Authorization: Bearer followed by the value. Nothing else is read. Older examples add a New-Api-User header, which is ignored, and there is no cookie fallback.
Send that header even where the endpoint would answer without one. A request to an account route that carries no credential can be challenged before it reaches us, and a challenge answers HTML instead of JSON.
Open Settings, find API access under Security, and generate one. It is shown once. It can only be created while you are signed in, so a script cannot mint its own.
You hold one access token at a time. Generating a new one silently invalidates the old one, and anything still using it stops working.
Treat it like a password. It can spend money, create keys and read your history. Revoke it from the same screen once a script no longer needs it.
One public request returns every model, with prices already converted to dollars per million tokens. No key, no signup.
curl https://api.unorouter.com/api/pricing/catalog{
"counts": { "models": 239, "free": 134, "paid": 105, "vendors": 50 },
"first_free_model": "glm-5.3:free",
"vendors": [{ "vendor_id": 4, "vendor_name": "Zhipu", "icon": "Zhipu.Color" }],
"models": [
{
"model_name": "glm-5.3",
"vendor": "Zhipu",
"type": "text",
"is_free": false,
"online": true,
"input_price": 0.05103,
"output_price": 0.160382187,
"original_input_price": 1.26,
"original_output_price": 3.960054,
"tags": "Text,Reasoning,Tools,Cache",
"supported_endpoint_types": ["openai", "anthropic"],
"uptime_24h": 99.965,
"success_rate": 95,
"avg_latency_ms": 11062
}
]
}input_price and output_price are what you would actually pay today, at the cheapest provider currently serving that model. original_input_price and original_output_price are the vendor's own list price, so the gap between the two is the discount. is_free marks models that never charge, and online marks the ones with a live provider right now.
Two smaller relatives: /api/pricing/counts returns only the totals and is cheap enough to poll, and /api/pricing/vendors returns the vendor list. The older /api/pricing endpoint returns raw ratios rather than dollars and is several times larger.
Most models are served by several providers at different rates. Ask for a single model to see all of them.
curl "https://api.unorouter.com/api/pricing/catalog/model?model=glm-5.3"{
"model_name": "glm-5.3",
"model_ratio": 0.63,
"completion_ratio": 3.1429,
"cache_ratio": 0.1857,
"input_price": 0.05103,
"output_price": 0.160382187,
"grid_min_ratio": 0.0405,
"auto_chain": ["a7-bbgt-2846-glm-5.3", "a7-kkl-3731-glm-5.3", "a7-4069-glm-5.3"],
"group_ratio": {
"a7-bbgt-2846-glm-5.3": 0.0405,
"a7-kkl-3731-glm-5.3": 0.0911,
"a7-4069-glm-5.3": 0.1214
},
"enable_groups": ["a7-bbgt-2846-glm-5.3", "a7-kkl-3731-glm-5.3"]
}auto_chain is the order routing tries them in, cheapest first. group_ratio gives each provider's multiplier, and the price follows from it:
input $/1M = model_ratio * 2 * group_ratio
output $/1M = input * completion_ratio
cached $/1M = input * cache_ratio
glm-5.3 on a7-bbgt-2846: 0.63 * 2 * 0.0405 = $0.05103 / 1M in
0.05103 * 3.1429 = $0.16038 / 1M outThese group names are the same ones you pin a key to, see Group Pinning
The OpenAI compatible list is what most clients call when you press connect. It needs an API key.
curl https://api.unorouter.com/v1/models \
-H "Authorization: Bearer $UNOROUTER_API_KEY"It returns only what that key may actually use, so a free tier key sees fewer models than a funded one. Each entry also carries context_length and max_output_tokens, which plain OpenAI does not provide.
The path is /v1/models. Drop the /v1 and you reach a web page rather than the API, which clients report as a JSON parse error about an unexpected doctype.
One call returns your account, including what is left to spend.
curl https://api.unorouter.com/api/user/self \
-H "Authorization: Bearer $UNOROUTER_ACCESS_TOKEN"{
"success": true,
"message": "",
"data": {
"id": 12345,
"username": "you",
"group": "default",
"quota": 265000,
"used_quota": 4231900,
"request_count": 1884,
"aff_code": "ABC123"
}
}Amounts are in quota units, not dollars. 500,000 units is one US dollar, so the balance above is $0.53. Divide by 500,000 to display money.
quota is what you can still spend. used_quota and request_count are lifetime totals that only ever rise, so used_quota is not the difference between anything and your balance.
List your keys, then reveal one by id.
curl "https://api.unorouter.com/api/token/?p=1&page_size=20" \
-H "Authorization: Bearer $UNOROUTER_ACCESS_TOKEN"
curl -X POST https://api.unorouter.com/api/token/42/key \
-H "Authorization: Bearer $UNOROUTER_ACCESS_TOKEN"Listing masks every key value, which is why revealing is a separate call. That call is rate limited and recorded in your audit log.
Creating a key returns success but no key. Create it, list to find its id, then reveal it. Updating a key returns the updated object, but send the whole object: only cross_group_retry, group_mapping and auto_groups survive being left out, and any other field you omit is written back empty.
Your own request history is paged and filterable.
curl -G https://api.unorouter.com/api/log/self \
-H "Authorization: Bearer $UNOROUTER_ACCESS_TOKEN" \
-d p=1 \
-d page_size=100 \
-d type=2 \
-d start_timestamp=1789344000 \
-d model_name=glm-5.3| Parameter | Meaning |
|---|---|
type | 1 topup, 2 consume, 3 manage, 5 error, 6 refund, 7 login. Omit it for everything. |
start_timestamp, end_timestamp | Unix seconds. The two may not span more than ten years. |
model_name, token_name, group | Narrow to one model, one key, or one provider group. |
request_id, upstream_request_id | Find a single request, by our id or the provider's. |
Add p and page_size for paging, where page size is capped at 100. A sibling endpoint, /api/log/self/stat, returns only the totals for the same filters: spend, requests per minute and tokens per minute.
To read one key's remaining balance without an access token, authenticate with the key itself:
curl https://api.unorouter.com/api/usage/token/ \
-H "Authorization: Bearer $UNOROUTER_API_KEY"{
"object": "token_usage",
"name": "SillyTavern",
"total_granted": 500000,
"total_used": 231900,
"total_available": 268100,
"unlimited_quota": false,
"model_limits_enabled": false,
"expires_at": 0
}Account endpoints wrap their payload. Paged ones wrap it twice.
{ "success": true, "message": "", "data": { ... } }
{ "success": true, "message": "", "data": {
"page": 1, "page_size": 20, "total": 137, "items": [ ... ]
} }The public catalog endpoints are the exception and return their object directly, with no wrapper.
Most failures still answer HTTP 200 with success set to false and a message explaining why. Check that field rather than the status code.
Authentication is the exception and answers a real status: 401 with AUTH_UNAUTHORIZED for a bad credential, AUTH_TOKEN_EXPIRED for a stale one, AUTH_SESSION_REVOKED after a logout, and 403 AUTH_INSUFFICIENT_PRIVILEGE when the account may not call that route.