#User Guide
One base URL that speaks both major LLM API dialects and routes everything to a curated OpenRouter preset. You bring your own OpenRouter API key; the gateway stores nothing.
- Base URL:
https://api.badlandslabs.com - You need: an OpenRouter API key (
sk-or-v1-...) — valid customers receive instructions for obtaining their OpenRouter API key via email
Where to go by plan:
| You are… | Getting started |
|---|---|
| Enterprise (AI-native workspace) | Once your workspace is deployed, log in through your console. |
| Managed workspace | Refer to your enterprise plan and package for access details. |
| API user | You're in the right place — this documentation covers everything, starting with Authentication below. |
#Authentication
Send your OpenRouter key in either header style — use whichever your client emits:
| Style | Header | Typical clients |
|---|---|---|
| OpenAI | Authorization: Bearer sk-or-v1-... |
OpenAI SDK, cline, most CLIs |
| Anthropic | x-api-key: sk-or-v1-... |
Anthropic SDK, Claude-compatible tools |
If your environment leaks both headers (e.g. an ambient ANTHROPIC_AUTH_TOKEN plus an
explicit key), the gateway prefers whichever value looks like an OpenRouter key (sk-or-
prefix), so mixed-credential setups still work.
Your key is forwarded to OpenRouter per-request over TLS and is never stored or logged.
#Models
The catalog spans four families. Valid model ids include:
| Family | Model ids |
|---|---|
| Anthropic | claude-fable-5, claude-opus-4-8, claude-sonnet-5, claude-haiku-4-5 |
| OpenAI | gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-5.5, gpt-5.4, gpt-5.3-codex, gpt-5.2, gpt-5.1, gpt-4o |
| MiniMax | minimax-m3, MiniMax-M2.7, minimax-m2.5, minimax-m2 |
| GLM (Zhipu) | glm-5.2, glm-5.1, glm-5, glm-4.7 |
Full catalog: API.md, or query it live:
These model ids are compatibility labels for clients. The current configured upstream fusion/fallback route uses MiniMax only; callers cannot select a different upstream by changing the public label.
curl https://api.badlandslabs.com/v1/models \
-H "authorization: Bearer $OPENROUTER_API_KEY"
Then send the id you picked in your request:
curl https://api.badlandslabs.com/v1/chat/completions \
-H "authorization: Bearer $OPENROUTER_API_KEY" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-5",
"messages": [{"role": "user", "content": "Hello!"}]
}'
#Endpoints
| Method & path | Dialect | Notes |
|---|---|---|
POST /v1/messages |
Anthropic Messages | streaming + non-streaming, tools, system prompts |
POST /v1/messages/count_tokens |
Anthropic | returns an estimate (~chars/4) |
POST /v1/chat/completions |
OpenAI Chat Completions | streaming + non-streaming, tool calling |
GET /v1/models, GET /v1/models/{id} |
both | shape follows your dialect (see below) |
GET /health |
— | gateway status, no auth |
any other /v1/* |
— | proxied to OpenRouter with response identity filtering |
GET /v1/models answers in the Anthropic list shape when you send an
anthropic-version header, otherwise in the OpenAI list shape.
#Examples
#curl — Anthropic format
curl https://api.badlandslabs.com/v1/messages \
-H "x-api-key: $OPENROUTER_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-5",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello!"}]
}'
Streaming ("stream": true) emits the standard Anthropic SSE event sequence:
message_start → content_block_start → content_block_delta … → message_delta → message_stop.
#curl — OpenAI format
curl https://api.badlandslabs.com/v1/chat/completions \
-H "authorization: Bearer $OPENROUTER_API_KEY" \
-H "content-type: application/json" \
-d '{
"model": "gpt-4o",
"stream": true,
"messages": [{"role": "user", "content": "Hello!"}]
}'
Streaming emits standard data: {...} chunks ending with data: [DONE]. You may see
: OPENROUTER PROCESSING keepalive comments — SSE-spec-compliant, ignore them.
#Python
# Anthropic SDK
from anthropic import Anthropic
client = Anthropic(api_key=OPENROUTER_API_KEY, base_url="https://api.badlandslabs.com")
msg = client.messages.create(
model="claude-sonnet-5", max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}],
)
# OpenAI SDK
from openai import OpenAI
client = OpenAI(api_key=OPENROUTER_API_KEY, base_url="https://api.badlandslabs.com/v1")
res = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
)
Note the difference: Anthropic SDKs take the bare base URL, OpenAI SDKs take base URL +
/v1.
#Node / TypeScript
import Anthropic from "@anthropic-ai/sdk";
import OpenAI from "openai";
const anthropic = new Anthropic({ apiKey: KEY, baseURL: "https://api.badlandslabs.com" });
const openai = new OpenAI({ apiKey: KEY, baseURL: "https://api.badlandslabs.com/v1" });
#Working with coding tools
Agentic coding tools — Claude Code, opencode, cline — work with the gateway out of the box and are verified end-to-end. Setup for each lives on its own page: Coding Tools.
#Reasoning models
The preset may point at a reasoning model. The gateway removes native Anthropic
thinking/redacted_thinking blocks, <think>/<analysis> text, known provider/model
mentions, and the resolved upstream model field before returning either dialect. It
buffers streams so identity text split across chunks is still removed. The gateway also
sends "reasoning": {"exclude": true} upstream for OpenAI requests to keep reasoning
out of the final-answer channel; it remains the final filter either way.
#Errors
Errors arrive in your dialect's native shape:
// Anthropic callers // OpenAI callers
{ "type": "error", { "error": {
"error": { "type": "...", "message": "...",
"message": "..." } } "type": "...", "code": null } }
| Status | Meaning |
|---|---|
401 Missing API key... |
You didn't send a key in either header |
401 User not found. |
OpenRouter rejected your key — check/rotate it |
| 402 | OpenRouter: out of credits |
404 No endpoints found... |
The preset is misconfigured upstream — contact the operator |
| 429 | OpenRouter rate limit |
| 502 | Gateway couldn't reach OpenRouter |
#FAQ
Which model actually answers? Whatever the operator's OpenRouter preset selects.
The response model field is caller-facing and does not reveal the resolved upstream.
Is my key safe? It transits the worker in memory per-request over TLS and is never persisted or logged. Rotate it on OpenRouter at any time.
Token counting? /v1/messages/count_tokens returns an estimate (~4 chars/token),
because OpenRouter doesn't implement it. Budget accordingly.