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.

first-call.sh
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.

python
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="")
typescript
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.

models.sh
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.

FieldBehaviour
model, messages, tools, tool_choice: auto | none, max_tokens, stop, temperature, response_format: json_schema, stream, stream_optionsHonoured
tool_choice: a named functionAccepted 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_tokensReported when the provider reports it. DEEP currently answers 0 even on a cache hit; a zero is not metered.
POST /v1/messages/count_tokensAvailable. 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.

opencode.json
{
  "$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.

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

Create a key