Documentation
BreignHUB speaks the OpenAI wire format. Point any client at the base URL below, use a BreignHUB key as the API key, and the rest of your code stays as it is.
Base URL and authentication
Your key goes in the Authorization header as a bearer token. Keys are created in the dashboard and shown once — BreignHUB does not store the secret and cannot show it to you again.
- Base URL
- https://api.hub.breign.eu/v1
- API key
- A key from the dashboard, starting with otoapk_
Call it from your server, not from a browser
The gateway answers browsers on this site's own origin only, for the dashboard's test console. That is deliberate: a BreignHUB key in front-end code is a key you have published. Keep it on your own server, or behind a proxy you control.
Your first call
Model ids are plain names, exactly as GET /models lists them: no provider prefix. Read them there, or from the catalogue.
curl https://api.hub.breign.eu/v1/chat/completions \ -H "Authorization: Bearer $BREIGNHUB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "<model>", "messages": [{ "role": "user", "content": "Say hello." }] }'
From the OpenAI SDK
Nothing but the base URL changes. Streaming works the same way.
from openai import OpenAI client = OpenAI( base_url="https://api.hub.breign.eu/v1", api_key=os.environ["BREIGNHUB_API_KEY"], ) stream = client.chat.completions.create( model="<model>", messages=[{"role": "user", "content": "Say hello."}], stream=True, ) for chunk in stream: print(chunk.choices[0].delta.content or "", end="")
import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://api.hub.breign.eu/v1", apiKey: process.env.BREIGNHUB_API_KEY, }); const stream = await client.chat.completions.create({ model: "<model>", messages: [{ role: "user", content: "Say hello." }], stream: true, }); for await (const chunk of stream) { process.stdout.write(chunk.choices[0]?.delta?.content ?? ""); }
Listing models
Returns the models the key’s organization can reach. Model availability comes from the organization, not from the key.
curl https://api.hub.breign.eu/v1/models \ -H "Authorization: Bearer $BREIGNHUB_API_KEY"
What the gateway honours
Measured against the gateway, not assumed from a specification. The fields below behave as documented by OpenAI unless the table says otherwise.
| Field | Behaviour |
|---|---|
| model, messages, tools, tool_choice: auto | none, max_tokens, stop, temperature, response_format: json_schema, stream, stream_options | Honoured |
| tool_choice: a named function | Accepted but not enforced by the inference engine: the model may call another tool. Check the tool name in the answer. |
| finish_reason: "length" | Reported. A non-streamed answer cut short by max_tokens ends with "length" (Anthropic: "max_tokens"), as the provider said. |
| usage.prompt_tokens_details.cached_tokens | Reported when the provider reports it. DEEP currently answers 0 even on a cache hit; a zero is not metered. |
| POST /v1/messages/count_tokens | Available. The prompt is measured by the provider itself, with a one-token call: exact, but metered and billed like any other call. |
Using BreignHUB from a coding agent
Any tool that lets you set an OpenAI-compatible base URL works. Two examples.
opencode
Add a provider to opencode.json. Use @ai-sdk/openai-compatible, which targets /v1/chat/completions. Mark the model tool_call so opencode calls tools natively, then run /models and pick it.
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"breignhub": {
"npm": "@ai-sdk/openai-compatible",
"name": "BreignHUB",
"options": {
"baseURL": "https://api.hub.breign.eu/v1",
"apiKey": "{env:BREIGNHUB_API_KEY}"
},
"models": {
"<model>": {
"name": "Qwen3.6 35B",
"tool_call": true
}
}
}
}
}export BREIGNHUB_API_KEY=otoapk_… · opencode · /models
Anything else
Continue, Cline, aider, LangChain, the Vercel AI SDK — all of them take a base URL and an API key. Set those two and you are done. Tool calling and response_format: json_schema work. The one thing the gateway does not enforce is a tool_choice that forces a particular function: the model may call another tool, so check the tool name in the answer.
Errors
Failures do not use the OpenAI envelope. A missing or rejected key answers 400 with an Otoroshi-Error field; a malformed body answers 400 with error and error_details; an error raised by the inference cluster is passed through as the cluster produced it.
# no key, or a key the gateway rejects — 400 { "Otoroshi-Error": "invalid apikey tuple" } # a body that is not valid JSON — 400 { "error": "bad_request", "error_details": "Unexpected end-of-input…" } # an error raised by the inference cluster, passed through { "detail": "Model not found" }
Create a key
Keys are created and revoked from the dashboard.