One base URL. Three dialects.
Authenticate with a backchannel API key and call a model by its slug. The proxy picks an upstream route, translates the request, and translates the response back.
Authentication
Create an API key in your dashboard. Keys start with bcl_ and are shown once. Send one of:
Authorization: Bearer bcl_...
# or, for Anthropic clients:
x-api-key: bcl_...
anthropic-version: 2023-06-01The key belongs to your account. Requests are billed to your balance using the model's base price and your account discount.
Models
Use the exact model slug from the catalog. The same slug works across all three APIs. List them with:
curl https://backchannel.lol/v1/models \
-H "Authorization: Bearer $BACKCHANNEL_KEY"Chat Completions
curl https://backchannel.lol/v1/chat/completions \
-H "Authorization: Bearer $BACKCHANNEL_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "your-model",
"messages": [
{ "role": "system", "content": "Be concise." },
{ "role": "user", "content": "What is a backchannel?" }
],
"stream": true
}'Responses
Supports string or item-array input, instructions, function tools, and streaming response events.
curl https://backchannel.lol/v1/responses \
-H "Authorization: Bearer $BACKCHANNEL_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "your-model",
"instructions": "Be concise.",
"input": [
{ "role": "user", "content": "What is a backchannel?" }
],
"stream": true
}'Anthropic Messages
Supports system blocks, text/image/tool_use/tool_result content, stop sequences, and the Messages stream. max_tokens defaults to 4096 if omitted.
curl https://backchannel.lol/v1/messages \
-H "x-api-key: $BACKCHANNEL_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "your-model",
"max_tokens": 512,
"system": "Be concise.",
"messages": [
{ "role": "user", "content": "What is a backchannel?" }
],
"stream": true
}'SDK setup
Use the vendor SDKs by changing only the base URL and key. All translation happens at the proxy.
from openai import OpenAI
client = OpenAI(
api_key="bcl_...",
base_url="https://backchannel.lol/v1",
)
resp = client.chat.completions.create(
model="your-model",
messages=[{"role": "user", "content": "hello"}],
)
print(resp.choices[0].message.content)from anthropic import Anthropic
client = Anthropic(
api_key="bcl_...",
base_url="https://backchannel.lol",
)
msg = client.messages.create(
model="your-model",
max_tokens=512,
messages=[{"role": "user", "content": "hello"}],
)
print(msg.content[0].text)const res = await fetch("https://backchannel.lol/v1/responses", {
method: "POST",
headers: {
"authorization": `Bearer ${process.env.BACKCHANNEL_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({
model: "your-model",
input: "hello",
}),
});
const data = await res.json();
console.log(data.output);Translation
Each model/provider route declares a native API: chat, responses, or messages. Any incoming dialect is translated to the route's native dialect, and the response is translated back. Streaming uses a canonical event stream, so text, tool calls, usage, and stop reasons survive across formats.
| Incoming | Native upstream | Streaming |
|---|---|---|
| chat | chat / responses / messages | SSE translated |
| responses | chat / responses / messages | SSE translated |
| messages | chat / responses / messages | SSE translated |
stream_options.include_usage from chat-native upstreams. If an upstream omits usage, the proxy estimates tokens from the content and flags the row as metered, not provider-reported.Pricing & billing
Prices are per million input and output tokens. New accounts start at 90% off. Your discount is set by lifetime top-ups and locked for the life of the account; admin overrides can supersede it. Models can set a separate cached-input price; cached reads use that instead of the normal input price.
A request costs:
non_cached_input = input_tokens - cached_input_tokens
cached_price = model.cached_input_price ?? input_price
base = ceil((non_cached_input * input_price + cached_input_tokens * cached_price + output_tokens * output_price) / 1,000,000)
cost = ceil(base * (1 - discount))Balances are prepaid. Top up with an on-chain invoice from the billing page, or an admin can adjust the ledger. A request is rejected with 402 when the balance is at or below zero; an in-flight request can take the balance slightly negative.
Errors
Errors use the shape of the incoming API. Streaming requests return a normal JSON error before the stream commits; after commit, the error is delivered as an SSE event and the request is logged as failed.
| Status | Meaning |
|---|---|
| 400 | Invalid request or unsupported content/tool type. |
| 401 | Missing or invalid API key. |
| 402 | Insufficient balance. |
| 404 | Unknown or disabled model. |
| 429 | Per-key rate limit, or every upstream is rate limited. |
| 502 | All upstream routes failed. |
| 504 | Upstream timed out. |
Privacy
The proxy logs only usage metadata: timestamp, user, key id, model, provider, API dialect, stream flag, success, status, token counts, cost, latency, TTFT, tokens/sec, error code, and attempt count.
It does not store request bodies, response bodies, prompts, completions, headers, IP addresses, or user agents. Upstream credentials are encrypted at rest. API keys are stored as SHA-256 hashes and shown once.
Detailed request metadata is capped by row count so the database stays bounded at high throughput. Aggregate hourly and daily stats, cost, token, latency, error, and uptime totals are kept permanently.