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.

header
Authorization: Bearer <your key>

Getting a key

  • From the installer. The one-line install writes a shared public key into ~/.chainlayer/config.json as apiKey. 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 login binds the CLI to your account with a device-code flow. Self-serve keys for other clients: sign in at chainlayertwo.online/account and 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

GEThttps://chainlayertwo.online/v1/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
curl https://chainlayertwo.online/v1/models \
  -H "Authorization: Bearer $CHAINLAYER_API_KEY"

Chat completions

POSThttps://chainlayertwo.online/v1/chat/completions

The standard OpenAI request body. The fields that matter most:

FieldNotes
modelRequired. An id from /v1/models, or a role route such as auto/best-coding.
messagesRequired. system, user, assistant and tool roles.
streamtrue returns server-sent events: data: {chunk} lines, ending with data: [DONE].
tools, tool_choiceFunction 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_tokensPassed through to the upstream model.
logprobs, top_logprobsPassed 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 · streaming + tools
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.
JavaScript · npm i openai
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));
}
Tool-call arguments arrive in pieces across chunks; join them by index before parsing.
Python · pip install openai
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)
The SDKs retry some failures on their own. For 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:

StatusMeaningWhat to do
401 AUTH_002No 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.
403Account requests only: the account is still pending approval.Wait for an admin to approve it. See tiers.
429Account 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_busyThe 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, otherNo upstream provider could serve the model you asked for.Try another model or a role route, or retry later.
503 at capacity
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 / adminNo daily cap
Local providerNot 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.

EndpointReturns
GET /stats/v1/summaryHeadline numbers: version, uptimePct30d, requests24h, tokens24h, modelsOnline, providersHealthy, generatedAt.
GET /stats/v1/timeseries?range=24hTraffic over time. range is 24h, 7d or 30d.
GET /stats/v1/modelsPer-model aggregates for the models currently in the catalog.
GET /stats/v1/healthCurrent 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.

GET /stats/v1/summary
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.

shell
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 idWhereKey
ollama127.0.0.1:11434none
llamacpp127.0.0.1:8080none
lmstudio127.0.0.1:1234none
deepseekapi.deepseek.comDEEPSEEK_API_KEY
kimiapi.moonshot.aiMOONSHOT_API_KEY
anthropicapi.anthropic.comANTHROPIC_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.