#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.

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.