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.

HTTPcodeWhat to do
400, 413, 422invalid_requestYour 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.
401invalid_keyKey unknown or revoked. Check the header, then contact support.
402insufficient_creditsBalance cannot cover the pre-authorization. Top up — credits are prepaid.
403frozenAccount frozen by safety. Contact support; credits stay intact.
404model_not_foundThe id is absent or not live. See Models.
404not_foundThe path does not exist. Check the URL — the API paths are under /v1; a bad model id is model_not_found, not this.
405invalid_requestWrong method for any path that exists. Check the HTTP method the endpoint expects.
429rate_limitedYour key hit the gateway's per-key limit. Back off and retry.
500internal_errorOur bug, not your request — the traceback stays in our logs. Retry once, then contact support; the request was not billed.
502upstream_errorThe 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.

ModelInput / 1M tokensOutput / 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.