Docs · first call
From key to first call.
You have a key. Here is the whole integration: the base URL, the auth header, one copy-paste curl, a Python snippet with the OpenAI SDK, the live model id, and the error codes that change what you should do next.
The key is shown once, on the welcome page after checkout. It cannot be displayed again — store it before you close the tab.
1. Base URL
The key-facing API is an OpenAI-style surface under /v1:
/v1/chat/completions, /v1/models and /v1/usage.
The same gateway also exposes billing and operations routes outside /v1 —
/billing/checkout, /billing/webhook,
/welcome, /admin/* and /healthz — none of which
are called with your key.
- Local gateway:
http://127.0.0.1:8081/v1. - Deployed gateway:
https://api.unbarred.dev/v1— the hostname in front of your deployment.
Both snippets below set the base URL themselves, including the /v1 suffix:
the curl block puts it in UNBARRED_BASE_URL, the Python snippet sets
base_url directly. Change that one value in the snippet you use if you run
the gateway locally.
2. Authentication
Send the key as a bearer token on every authenticated request. GET /v1/models is the only public route on the inference API:
Authorization: Bearer sk-...
- Keys are
sk-followed by 32 hex characters. - The key is minted on the welcome page after a paid checkout, and shown exactly once. There is no "show my key" route — store it in a secret manager, not in git.
- A missing or unknown key is
401 invalid_key.
3. First call — curl
This is the whole request: a chat completion with one user message. Paste your key into the key export, and check that the gateway URL is the one you want.
# Your key is shown once, on the welcome page after a paid checkout.
# Paste it here, not into git.
export NEELAB_API_KEY="sk-REPLACE_ME"
# Gateway root, /v1 included. Local: http://127.0.0.1:8081/v1
export UNBARRED_BASE_URL="https://api.unbarred.dev/v1"
curl -sS "$UNBARRED_BASE_URL/chat/completions" \
-H "Authorization: Bearer $NEELAB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4.1-flash-abliterated",
"messages": [{"role": "user", "content": "Say hello."}]
}'
A successful call returns JSON; the reply is at choices[0].message.content.
4. Python (OpenAI SDK)
The gateway is the OpenAI chat-completions subset, so the official SDK works unchanged. One dependency:
pip install openai
Point base_url at the gateway root, /v1 included, and use your key as api_key.
from openai import OpenAI
client = OpenAI(
base_url="https://api.unbarred.dev/v1", # local: http://127.0.0.1:8081/v1
api_key="sk-REPLACE_ME",
)
completion = client.chat.completions.create(
model="deepseek-v4.1-flash-abliterated",
messages=[{"role": "user", "content": "Say hello."}],
)
print(completion.choices[0].message.content)
5. Streaming is byte-identical
Add stream: true to the same JSON body and the response is a
text/event-stream. The bytes are the upstream model's: the gateway parses the
stream to settle the charge on the tokens actually emitted, but never re-emits, re-encodes
or re-frames the data. What you receive is what the model produced.
Without stream you get the same completion as a single JSON response.
6. Errors
Errors share one shape: {"error": {"code": "...", "message": "..."}}. Branch on the HTTP status and the code; treat message as human text.
| HTTP | code | What to do |
|---|---|---|
| 400, 413, 422 | invalid_request | Your request was rejected: malformed JSON or a missing field, or the model judged the input invalid, too large or unprocessable. The message says which; a rejected request is not billed. |
| 401 | invalid_key | Key unknown or revoked. Check the header, then contact support. |
| 402 | insufficient_credits | Balance cannot cover the pre-authorization. Top up — credits are prepaid. |
| 403 | frozen | Account frozen by safety. Contact support; credits stay intact. |
| 404 | model_not_found | The id is absent or not live. See Models. |
| 404 | not_found | The path does not exist. Check the URL — the API paths are under /v1; a bad model id is model_not_found, not this. |
| 405 | invalid_request | Wrong method for any path that exists. Check the HTTP method the endpoint expects. |
| 429 | rate_limited | Your key hit the gateway's per-key limit. Back off and retry. |
| 500 | internal_error | Our bug, not your request — the traceback stays in our logs. Retry once, then contact support; the request was not billed. |
| 502 | upstream_error | The model behind the gateway failed, timed out, or was rate-limited on the model's side; that limit arrives here, not as 429. Retry with backoff — a failed model call is refunded. The same code covers gateway-side failures outside inference: a missing gateway or billing configuration, a payment provider that cannot create the checkout session, or an unavailable bot check. Those are not model failures; retrying may not help, so contact support. |
The two that change what you do: 402 means top up, 403 means contact support. They are deliberately different codes, so a client can tell a balance problem from a policy problem.
7. Models
Your key works with every live model below. GET /v1/models returns the same
live list and needs no auth.
| Model | Input / 1M tokens | Output / 1M tokens |
|---|---|---|
DeepSeek V4.1 Flash Abliterateddeepseek-v4.1-flash-abliterated | $1.00 | $3.00 |
This table is generated from models.toml by web/build.py;
prices are per 1M tokens, input and output billed separately.
8. Use it from an agent
Agent clients speak one of three shapes: OpenAI chat completions, the Responses API, or Anthropic Messages. This gateway serves only the first, so the paths are: an OpenAI-SDK agent that speaks chat completions (works today), Codex CLI on 0.94.0 or earlier, and Claude Code behind a proxy. The version constraints are stated with each setup.
Codex CLI — 0.94.0 or earlier only
Current Codex CLI cannot connect directly. Codex 0.95 removed
wire_api = "chat" and speaks only the Responses API
(/v1/responses), which this gateway does not serve. A newer CLI rejects this
configuration at startup. Pin 0.94.0 if you want this setup:
npm i -g @openai/codex@0.94.0. Otherwise use the OpenAI-SDK path below.
On 0.94.0 and earlier, add this provider to ~/.codex/config.toml. It sends
POST /v1/chat/completions to the gateway with your key.
# ~/.codex/config.toml
model = "deepseek-v4.1-flash-abliterated"
model_provider = "neelab"
[model_providers.neelab]
name = "Neelab"
base_url = "https://api.unbarred.dev/v1"
env_key = "NEELAB_API_KEY"
wire_api = "chat"
Export the key the block reads — export NEELAB_API_KEY="sk-..." — then run
codex.
OpenAI-SDK agents — works today
Any OpenAI SDK or agent framework that speaks chat completions needs two values: the
gateway root from
section 1 as its base URL and your key as its API key. The
Python snippet in section 4 is exactly that setup; any other SDK or
framework takes the same base_url and key, plus the model id from
Models.
Tool calling is up to the model behind the gateway. For a model served upstream, the
gateway rewrites the model field to the vendor's model id and replaces the
Authorization header with the upstream key; the rest of the request body is
forwarded as sent. Whether a tool-call request works depends on how that model handles tools.
Claude Code — not supported; use a proxy
Claude Code speaks the Anthropic Messages API (POST /v1/messages). This
gateway does not serve that shape, and pointing Claude Code at our base URL will not
work — no key and no environment variable changes that.
If you need Claude Code against these models, put an Anthropic-Messages proxy in front:
LiteLLM (its proxy exposes /v1/messages and can route to OpenAI-compatible
models) or claude-code-router. Claude Code talks to the proxy; the proxy calls our
chat-completions endpoint with Authorization: Bearer. We do not ship either
proxy, and running one is another service you have to operate.