API reference
One OpenAI-compatible endpoint.
The hosted gateway speaks the OpenAI Chat Completions API. Point any OpenAI SDK at https://chainlayertwo.online/v1, send a bearer key, and pick a model from the catalog. This page covers what it accepts, what it returns when it refuses, and the public stats you can read without a key.
Authentication
Every /v1 request carries a key in the Authorization header. A request with no key gets 401.
Authorization: Bearer <your key>
Getting a key
- From the installer. The one-line install writes a shared public key into
~/.chainlayer/config.jsonasapiKey. Any client on that machine can use the same key. It is shared by every anonymous user, so it gets shared capacity. - From your account.
chainlayertwo loginbinds the CLI to your account with a device-code flow. Self-serve keys for other clients: sign in atchainlayertwo.online/accountand create one under Gateway API keys (shown once; one key on a free account, up to five on privileged).
Keep keys out of source control. The examples below read the key from CHAINLAYER_API_KEY; set that yourself, it is not something the CLI reads.
List models
Returns the catalog in the OpenAI list shape: {"object":"list","data":[{"id":…,"object":"model",…}]}. The catalog changes with which upstream providers are reachable, so read it rather than hard-coding a list.
Alongside concrete model ids it includes role routes that pick a suitable backend for you, with fallback: auto/best-coding, auto/best-reasoning and auto/best-fast.
curl https://chainlayertwo.online/v1/models \
-H "Authorization: Bearer $CHAINLAYER_API_KEY"
Chat completions
The standard OpenAI request body. The fields that matter most:
| Field | Notes |
|---|---|
model | Required. An id from /v1/models, or a role route such as auto/best-coding. |
messages | Required. system, user, assistant and tool roles. |
stream | true returns server-sent events: data: {chunk} lines, ending with data: [DONE]. |
tools, tool_choice | Function calling in the OpenAI format. The reply's tool_calls are yours to run; send results back as tool messages. Not every model supports tools, so choose one that does. |
temperature, max_tokens | Passed through to the upstream model. |
logprobs, top_logprobs | Passed through, but many upstream providers ignore them and return null. Check the response before relying on them. |
Large requests are normal here: a coding-agent turn carries the whole conversation and tool definitions. If the gateway is at capacity it answers 503 with Retry-After before doing any work, so retrying after that delay is safe. See errors.
Examples
The same request three ways: a streamed chat with one tool the model may call.
curl https://chainlayertwo.online/v1/chat/completions \
-H "Authorization: Bearer $CHAINLAYER_API_KEY" \
-H "Content-Type: application/json" \
-N -d '{
"model": "auto/best-coding",
"stream": true,
"messages": [
{"role": "user", "content": "What is the weather in Oslo?"}
],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
}]
}'
-N turns off curl's buffering so SSE chunks print as they arrive.import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://chainlayertwo.online/v1",
apiKey: process.env.CHAINLAYER_API_KEY,
});
const stream = await client.chat.completions.create({
model: "auto/best-coding",
stream: true,
messages: [{ role: "user", content: "What is the weather in Oslo?" }],
tools: [{
type: "function",
function: {
name: "get_weather",
description: "Current weather for a city",
parameters: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
},
},
}],
});
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta;
if (delta?.content) process.stdout.write(delta.content);
if (delta?.tool_calls) console.log("\ntool call:", JSON.stringify(delta.tool_calls));
}
index before parsing.import os
from openai import OpenAI
client = OpenAI(
base_url="https://chainlayertwo.online/v1",
api_key=os.environ["CHAINLAYER_API_KEY"],
)
stream = client.chat.completions.create(
model="auto/best-coding",
stream=True,
messages=[{"role": "user", "content": "What is the weather in Oslo?"}],
tools=[{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}],
)
for chunk in stream:
delta = chunk.choices[0].delta if chunk.choices else None
if delta and delta.content:
print(delta.content, end="", flush=True)
if delta and delta.tool_calls:
print("\ntool call:", delta.tool_calls)
503, honour Retry-After rather than retrying immediately.Errors
Refusals come back as JSON with an HTTP status you can branch on. The ones you are likely to meet:
| Status | Meaning | What to do |
|---|---|---|
401 AUTH_002 | No key, or a key the gateway doesn't accept. Body: {"error":{"code":"AUTH_002","message":"Authentication required"}}. | Send Authorization: Bearer … with a valid key. Don't retry unchanged. |
403 | Account requests only: the account is still pending approval. | Wait for an admin to approve it. See tiers. |
429 | Account requests only: the user role's 5,000 requests for today are used up. Resets at midnight UTC. | Wait for the reset, or ask for the privileged role. |
503 chat_admission_busy | The gateway is at capacity and refused before doing any work. Sent with Retry-After (usually 2 seconds). | Wait Retry-After seconds and send the same request again. Back off if it repeats. |
503, other | No upstream provider could serve the model you asked for. | Try another model or a role route, or retry later. |
HTTP/1.1 503 Service Unavailable
Retry-After: 2
{"error":{"message":"Chat admission capacity is temporarily unavailable. Retry shortly.",
"type":"server_error","code":"chat_admission_busy"}}
Limits
Limits depend on how you authenticate. The full comparison, including what each tier needs, is on Access tiers.
| Tier | /v1 allowance |
|---|---|
| Anonymous (shared key) | Shared gateway capacity, no per-person quota |
Free account (user) | 5,000 requests per day, after approval |
| Privileged / admin | No daily cap |
| Local provider | Not metered: requests never reach this gateway |
There is no uptime or latency guarantee on any tier. Measured numbers are on Gateway stats.
Stats API
A public, read-only view of how the hosted gateway is doing. No key needed. It is CORS-open, so a browser page on any origin can call it, and responses are cached for 60 seconds. It only reports aggregates: no prompts, keys, accounts or IP addresses.
| Endpoint | Returns |
|---|---|
GET /stats/v1/summary | Headline numbers: version, uptimePct30d, requests24h, tokens24h, modelsOnline, providersHealthy, generatedAt. |
GET /stats/v1/timeseries?range=24h | Traffic over time. range is 24h, 7d or 30d. |
GET /stats/v1/models | Per-model aggregates for the models currently in the catalog. |
GET /stats/v1/health | Current health of the gateway and its providers. |
All paths are on https://chainlayertwo.online. Treat any field you don't recognise as optional, and expect new fields to appear.
Try it
This sends a real request from your browser and prints the response.
Press "Send request" to call the endpoint.
CLI and direct providers
The chainlayertwo CLI uses this gateway by default. Since release 0.5 it can also talk to a provider directly, without the gateway in between. Choose one per run with --provider, or name a model as provider/model.
chainlayertwo --provider ollama "say hi" # your local Ollama
chainlayertwo --provider kimi "..." # needs MOONSHOT_API_KEY
chainlayertwo --model ollama/qwen2.5-coder:0.5b "..." # explicit model
CHAINLAYER_PROVIDER=llamacpp chainlayertwo # same, via environment
chainlayertwo providers local # running servers + your GGUFs
| Provider id | Where | Key |
|---|---|---|
ollama | 127.0.0.1:11434 | none |
llamacpp | 127.0.0.1:8080 | none |
lmstudio | 127.0.0.1:1234 | none |
deepseek | api.deepseek.com | DEEPSEEK_API_KEY |
kimi | api.moonshot.ai | MOONSHOT_API_KEY |
anthropic | api.anthropic.com | ANTHROPIC_API_KEY |
Inside a session, /provider lists providers and /provider use <id> switches without losing the conversation. Cloud providers switch on only when their key is set, and local servers are never used for auto unless you configure them. The full guide is docs/LOCAL-MODELS.md in the ChainLayer2 source.