SDKs
Onlist provides official SDKs for Python and JavaScript/TypeScript. They wrap the
OpenAI client, so every chat.completions, embeddings, and images call works
out of the box with zero configuration: the base URL and auth header are handled
for you.
You do not need an SDK to use Onlist. Any OpenAI-compatible client works with
a base URL swap (see Quickstart and
Tool Configuration). The SDKs add convenience: auto-configured
endpoints, ONLIST_API_KEY env var support, a typed marketplace namespace
for browsing models and providers, and a typed account namespace for balance,
API keys, and usage.
Python
Install
pip install onlist
Usage
from onlist import Onlist
client = Onlist() # reads ONLIST_API_KEY env var
response = client.chat.completions.create(
model="anthropic/claude-sonnet-4",
messages=[{"role": "user", "content": "Hello!"}],
)
print(response.choices[0].message.content)
Authentication
The client looks for a key in this order:
api_keyconstructor parameterONLIST_API_KEYenvironment variableOPENAI_API_KEYenvironment variable (fallback for easy migration)
# Explicit key
client = Onlist(api_key="sk-...")
# Or set the env var
# export ONLIST_API_KEY=sk-...
client = Onlist()
Provider routing
Use extra_body to pass the provider object:
response = client.chat.completions.create(
model="openai/gpt-4o",
messages=[{"role": "user", "content": "Hello"}],
extra_body={"provider": {"sort": "price"}},
)
Streaming
stream = client.chat.completions.create(
model="anthropic/claude-sonnet-4",
messages=[{"role": "user", "content": "Tell me a story"}],
stream=True,
)
for chunk in stream:
content = chunk.choices[0].delta.content
if content:
print(content, end="", flush=True)
While a reasoning model is still thinking, the stream may open with SSE comment
heartbeats (: ONLIST PROCESSING). All official SDKs — Python, JavaScript, and the
Vercel AI SDK provider — skip comment lines automatically, so your chunk loop only ever
sees real chunks. If every upstream attempt fails after heartbeats began, the error
arrives as an in-stream error frame, which the SDKs raise as an API error. See
Streaming.
JavaScript / TypeScript
Install
npm install @onlist/sdk
Usage
import { Onlist } from "@onlist/sdk";
const client = new Onlist(); // reads ONLIST_API_KEY env var
const response = await client.chat.completions.create({
model: "anthropic/claude-sonnet-4",
messages: [{ role: "user", content: "Hello!" }],
});
console.log(response.choices[0].message.content);
Authentication
The client looks for a key in this order:
apiKeyconstructor parameterONLIST_API_KEYenvironment variableOPENAI_API_KEYenvironment variable (fallback for easy migration)
// Explicit key
const client = new Onlist({ apiKey: "sk-..." });
// Or set the env var
// export ONLIST_API_KEY=sk-...
const client = new Onlist();
Provider routing
Pass the provider field directly:
const response = await client.chat.completions.create({
model: "openai/gpt-4o",
messages: [{ role: "user", content: "Hello" }],
provider: { sort: "price" },
});
Streaming
const stream = await client.chat.completions.create({
model: "anthropic/claude-sonnet-4",
messages: [{ role: "user", content: "Tell me a story" }],
stream: true,
});
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content;
if (content) process.stdout.write(content);
}
Marketplace API
Both SDKs expose a marketplace namespace for browsing the Onlist catalog
programmatically.
# List models with pricing
models = client.marketplace.models.list(limit=10)
for m in models.data:
print(f"{m.id}: ${m.pricing.prompt}/M input tokens")
# Get a specific model with all provider offers
detail = client.marketplace.models.get("anthropic/claude-sonnet-4")
for offer in detail.providers:
print(f"{offer.name}: ${offer.price_input_usd}/M input")
# List providers
providers = client.marketplace.providers.list()
for p in providers.items:
print(f"{p.name} ({p.listing_count} models)")Rankings API
The same namespace exposes the usage leaderboards behind onlist.io/rankings.
# Model usage leaderboard: sort="popular" | "trending", window="day" | "week" | "month"
rankings = client.marketplace.rankings.models(sort="popular", window="week")
for entry in rankings.leaderboard:
print(f"#{entry.rank} {entry.model_name} — {entry.total_tokens} tokens")
# App usage rankings, optionally filtered by category
apps = client.marketplace.rankings.apps(sort="trending", window="month", category="coding")
for app in apps.apps:
print(f"#{app.rank} {app.title} ({app.domain})")Account API
Both SDKs wrap the Management API: balance, API key management, per-call costs, and daily usage.
Two credentials
Account endpoints authenticate with a management key (mgmt_...), which
is a different credential from your inference key (sk-...) and cannot make
model calls. Create one at Management keys.
client = Onlist(api_key="sk-...", management_key="mgmt_...")
# or set ONLIST_API_KEY and ONLIST_MANAGEMENT_KEYIf you pass only the API key, it is used for the account endpoints too. The
SDK never inspects key prefixes locally, so the server decides: where a
management key is actually required you get a PermissionDeniedError with
the server's own message.
Five namespaces
They follow the wire paths one-for-one:
| Namespace | Endpoint | What it does |
|---|---|---|
credits | /api/v1/credits | Account balance |
api_keys (apiKeys) | /api/v1/key, /api/v1/keys | Create, list, update, delete inference keys |
generations | /api/v1/generation | Cost and timing for one call |
activity | /api/v1/activity | Daily usage by model and provider |
oauth | /api/v1/auth/keys | Sign in with Onlist (PKCE exchange) |
credits = client.credits.get()
print(f"${credits.total_credits - credits.total_usage:.4f} remaining")
created = client.api_keys.create("ci-runner", limit=5.0, limit_reset="daily")
print(created.key) # plaintext, returned exactly once
for row in client.activity.list():
print(f"{row.date} {row.model} {row.requests} req ${row.usage:.4f}")generations.get() also accepts a plain inference key, which can look up the
calls it made itself. Pass the X-Oneapi-Request-Id response header value.
Updating a key: three states
update() distinguishes "leave this alone" from "clear this". An omitted
argument is not sent at all; an explicit None / null is sent as JSON null
and clears the value.
client.api_keys.update("42", limit=None) # remove the spend cap
client.api_keys.update("42", disabled=True) # stop it spending
client.api_keys.update("42", name="renamed") # limit untouchedIn Python the "omitted" state is openai's own NOT_GIVEN sentinel, so it
behaves exactly like optional arguments elsewhere in the openai client. In
TypeScript, undefined means omitted and null means clear.
name and disabled deliberately do not accept null: the server reads
{"name": null} as an empty name and {"disabled": null} as false, which
would re-enable a key you only meant to leave alone. The type signatures
prevent it.
Sign in with Onlist
Let your users authorize your app and receive their own inference key without pasting one. Both SDKs ship a PKCE helper.
import webbrowser
from onlist import Onlist, exchange_auth_code, generate_pkce
verifier, challenge = generate_pkce()
webbrowser.open(
"https://onlist.io/auth"
"?callback_url=http://localhost:8976/callback"
f"&code_challenge={challenge}&code_challenge_method=S256"
)
# ...user approves, your callback receives ?code=...
result = exchange_auth_code(code, code_verifier=verifier)
client = Onlist(api_key=result.key)The exchange is a standalone function rather than a client method because an
app running this flow has no API key yet — that is the whole point of it —
and constructing a client requires one. If you already have a client,
client.oauth.exchange() does the same thing.
The code is single-use and is consumed even when the verifier does not match, so a failed exchange means restarting the browser flow.
See Management API for the full field reference, limits, and the raw HTTP shape.
Migration from OpenAI
Replace the import and constructor. All existing method calls stay the same.
- from openai import OpenAI
+ from onlist import Onlist
- client = OpenAI(api_key="sk-...")
+ client = Onlist(api_key="sk-...")Migration from OpenRouter
Drop the baseURL override entirely.
- from openai import OpenAI
+ from onlist import Onlist
- client = OpenAI(
- base_url="https://openrouter.ai/api/v1",
- api_key=os.environ["OPENROUTER_API_KEY"],
- )
+ client = Onlist()