Gateway livePrices
(00) API referenceunderlist / v1

API
reference.

One base URL, one key, every model. The API speaks the OpenAI and OpenRouter formats: point your client at it and keep the rest of your code.

Base URL
https://api.undrlst.com/v1
Version
v1
Status
Gateway live
Compatible
OpenAI · OpenRouter

(01)

Quick start

Sign in on your account page, add credits and create a key. You see the key once: store it right away. Then send your first request.

Already on the OpenAI SDK or an OpenRouter client? Change the base URL and the key. Nothing else.

UseBase URL
Recommendedhttps://api.undrlst.com/v1
Also workshttps://undrlst.com/api/v1
MethodPathWhat it does
POST/chat/completionsRun a model. JSON or streamed.
GET/modelsEvery model you can call, OpenRouter format.
GET/keyYour key's balance and usage, OpenRouter format.
GET/auth/keySame as /key, for clients that use OpenRouter's path.
GET/creditsTotal credits added to your account and total spent.
Shell
export UNDERLIST_API_KEY=sk-smaaart-… curl https://api.undrlst.com/v1/chat/completions \  -H "Authorization: Bearer $UNDERLIST_API_KEY" \  -H "Content-Type: application/json" \  -d '{    "model": "anthropic/claude-sonnet-5",    "messages": [{ "role": "user", "content": "Say hi in five words." }]  }'

(02)

Authentication

Send your key in the Authorization header as a bearer token. Clients that use an x-api-key header work too.

Keys start with sk-smaaart-. Older keys starting with sk-inferrr- keep working until you rotate them.

One active key per account. Creating a new key revokes the old one in the same step, and the old key gets a 401 from its very next request. Your balance doesn't move. We only store a hash of the key and its last four characters, so we can't show it to you again.

Header
Authorization: Bearer sk-smaaart-…

(03)

Chat completions

POST/v1/chat/completions

The same request and response shapes as OpenAI's chat completions. The common fields are below; any other field the provider accepts is passed through.

FieldTypeDescription
modelstringRequired. The OpenRouter id of the model, e.g. anthropic/claude-sonnet-5. That exact model is called, never a substitute.
messagesarrayRequired. The conversation, as { role, content } objects, in the OpenAI format.
streambooleanSend tokens back as server-sent events while they are generated. Defaults to false.
temperature, top_p, max_tokens, …variousStandard sampling parameters, passed to the provider unchanged.
tools, tool_choice, response_formatvariousTool calls and structured output, passed through as is for models that support them.

Fixed on our side, so you always get what you asked for:

  • Zero-data-retention endpoints only. A provider object you send is kept, with zdr: true and data_collection: "deny" always set.
  • models, route and fallbacks are removed: one request runs one model, no silent fallback.
  • The response keeps the provider's own id and model, so you can check what served it.
  • Prompts and answers are never stored. We keep model, token counts, cost, latency and time.
TypeScript · openai
import OpenAI from "openai"; const client = new OpenAI({  baseURL: "https://api.undrlst.com/v1",  apiKey: process.env.UNDERLIST_API_KEY,}); const res = await client.chat.completions.create({  model: "openai/gpt-6-sol",  messages: [{ role: "user", content: "Say hi in five words." }],}); console.log(res.choices[0].message.content);

(04)

Streaming

Set stream: true to receive tokens as they are generated, as server-sent events. Every SDK that streams from OpenAI or OpenRouter works unchanged.

Each event is a data: line with a JSON chunk. The last chunk carries token usage and cost, and the stream ends with data: [DONE].

TypeScript · openai
const stream = await client.chat.completions.create({  model: "anthropic/claude-sonnet-5",  messages: [{ role: "user", content: "Write a haiku about caching." }],  stream: true,}); for await (const chunk of stream) {  process.stdout.write(chunk.choices[0]?.delta?.content ?? "");}
Response · text/event-stream
data: {"id":"gen-…","model":"anthropic/claude-sonnet-5","choices":[{"delta":{"content":"Cold"}}]} data: {"id":"gen-…","model":"anthropic/claude-sonnet-5","choices":[{"delta":{"content":" reads"}}]} data: {"id":"gen-…","choices":[],"usage":{"prompt_tokens":14,"completion_tokens":17,"cost":…}} data: [DONE]

(05)

Models

GET/v1/models

The full OpenRouter catalogue in OpenRouter's format, refreshed every hour. No key needed. Call any model by its id, like anthropic/claude-sonnet-5. Live prices are in the price list.

Shell
curl https://api.undrlst.com/v1/models

(06)

Balance and usage

Every response to a valid key, errors included, carries your balance in dollars in the X-Smaaart-Balance header (also sent as X-Inferrr-Balance for older integrations).

A request is accepted while your balance is above zero, and its real cost is deducted once it finishes. The last request before you run out can leave your balance slightly below zero.

GET/v1/key

Your key's balance and usage in OpenRouter's format, so tools that check a key before running work as is. /v1/auth/key returns the same thing.

GET/v1/credits

Everything ever added to your account and everything spent, in dollars.

Response header
X-Smaaart-Balance: 37.500000
GET /v1/key
curl https://api.undrlst.com/v1/key \  -H "Authorization: Bearer $UNDERLIST_API_KEY" {  "data": {    "label": "sk-smaaart-…AbCd",    "usage": 12.5,    "limit": null,    "limit_remaining": 37.5,    "is_free_tier": false  }}
GET /v1/credits
{  "data": {    "total_credits": 50,    "total_usage": 12.5  }}

(07)

Errors

Errors use the OpenAI shape, so your SDK raises them the way it already does. Provider errors pass through with their own status and body.

StatusWhat happened
400The body isn't a JSON object, or model is missing.
401The key is missing, malformed, unknown, revoked or replaced by a newer one.
402Your balance is used up. Top up and retry.
429The provider is rate-limiting the model. Relayed as is: wait and retry.
502The provider couldn't be reached, or returned an error (relayed with its own status and body). No fallback model is tried.
503underlist is temporarily unable to serve the request, for example while our upstream account is being topped up. Your balance is not charged.
Error body
{  "error": {    "message": "Insufficient balance, top up your credits",    "type": "insufficient_balance",    "code": 402  }}

Search underlist

Search pages, models, docs and questions