Hubzoid
Chat surfaces

OpenAI-compatible API

The bridge's /v1 chat completions API on 127.0.0.1, with its authentication, request fields, streaming, identity headers, attachments and uses.

Every hub's bridge speaks the OpenAI chat completions format at /v1. It is how the web chat, the Slack adapter and the WhatsApp and Telegram server talk to the agent, and you can call it from your own code on the same machine.

When to use this

Use the API for software that runs next to the hub: a script that produces a nightly stock summary, a run: script in a scheduled task that asks the agent to draft a message, or a local integration that needs the Hub's answer as text.

For people, use the web chat, Slack or WhatsApp and Telegram. For an assistant on someone's own computer, use MCP, which authenticates each person with their own key.

Where it listens

The bridge binds 127.0.0.1 only, on BRIDGE_PORT (8000 by default, or --bridge-port). The public port forwards /artifacts, /portal and, when MCP is on, /mcp to the bridge, never /v1, /uploads or /healthz. The Docker image publishes only port 3080. Under a gateway, each hub's bridge has its own loopback port.

Keep /v1 on loopback

A holder of the bridge key can call the agent as any identity, because the bridge trusts the identity headers described below. That is why the API stays off the public port. Give remote tools and people the Hub through the chat surfaces or MCP instead.

Authentication

Requests carry a bridge key as a Bearer token. Keys come from BRIDGE_API_KEYS in the hub's .env, a comma-separated list.

my-hub/.env
BRIDGE_API_KEYS=<output of: openssl rand -hex 32>
  • With no value set, the bridge accepts the public key dev and logs a warning. hubzoid doctor reports it as a failure (auth.bridge_keys). Set a random key for anything beyond your own laptop.
  • The chat app, the Slack adapter and the inbound server use the first key in the list, so you can add a second key for your scripts and rotate it separately.
  • A missing or wrong key returns 401 with {"detail": "invalid api key"}.
  • Key changes apply when the hub restarts.

Quick example

export HUBZOID_BRIDGE_KEY=<a key from BRIDGE_API_KEYS>

curl -s http://127.0.0.1:8000/v1/models \
  -H "Authorization: Bearer $HUBZOID_BRIDGE_KEY"
{
  "object": "list",
  "data": [
    { "id": "stock-desk", "object": "model", "created": 1790000000, "owned_by": "hubzoid" }
  ]
}

The single model is the hub's main agent. Its id is MODEL_LABEL when set, otherwise the name: from AGENTS.md in lowercase with hyphens.

curl -s http://127.0.0.1:8000/v1/chat/completions \
  -H "Authorization: Bearer $HUBZOID_BRIDGE_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Hubzoid-Surface: api" \
  -d '{
    "model": "stock-desk",
    "chat_id": "nightly-summary",
    "messages": [
      { "role": "user", "content": "Which items fell below their reorder point today?" }
    ]
  }'
{
  "id": "chatcmpl-3f1c...",
  "object": "chat.completion",
  "created": 1790000000,
  "model": "stock-desk",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "Three items are below reorder point..." },
      "finish_reason": "stop"
    }
  ],
  "usage": { "prompt_tokens": 1840, "completion_tokens": 212, "total_tokens": 2052 }
}

Streaming

Set "stream": true and read server-sent events. Use curl -N so output is not buffered.

curl -N http://127.0.0.1:8000/v1/chat/completions \
  -H "Authorization: Bearer $HUBZOID_BRIDGE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "stock-desk", "stream": true, "messages": [{"role": "user", "content": "Summarize yesterday"}]}'

The stream sends a first chunk with the assistant role, then one chat.completion.chunk per text delta, a chunk with "finish_reason": "stop", a final chunk with empty choices and the token usage, and data: [DONE].

Endpoints

Method and pathAuthPurpose
GET /healthznoneLiveness. Returns status, hub and agent.
GET /v1/modelsbridge keyThe hub's one model.
POST /v1/chat/completionsbridge keyRun the agent on a conversation, blocking or streamed.
POST /uploads/{chat_id}/{filename}bridge keyStore a file in a chat's uploads folder for the agent to read.
GET /artifacts/{chat_id}/{filename}signed link or bridge keyDownload a file the agent wrote. The public key dev is refused here.

The full list, including the Console and MCP routes, is in HTTP endpoints.

Request fields

Prop

Type

The bridge keeps no conversation state. Send the whole conversation each turn. It flattens the messages into one prompt, with system messages first, and runs the hub's agent on it. A system message from the caller becomes part of that prompt. It does not replace the hub's own instructions, knowledge or tools. Other OpenAI parameters, such as temperature, tools or response_format, are not applied: the hub's model settings and tools decide.

Chats, uploads and written files

Each request resolves a chat id, in this order: chat_id, metadata.chat_id (or metadata.conversation_id), the X-Hubzoid-Chat-Id header, user, and finally a hash of the first user message. Characters other than letters, digits, ., _ and - become -, and the id is cut to 64 characters. Files for that chat live in <hub>/.hubzoid/chats/<chat_id>/.

Attach a file inline as a base64 data URL. Images use an image_url part. Other files use an input_file or file part with a name.

{
  "role": "user",
  "content": [
    { "type": "text", "text": "Compare this count sheet with the system stock." },
    { "type": "input_file", "name": "count-sheet.csv", "data": "data:text/csv;base64,c2t1LGNvdW50ZWQK..." }
  ]
}

Or upload the raw file first and refer to it by name in the same chat:

curl -s -X POST http://127.0.0.1:8000/uploads/nightly-summary/count-sheet.csv \
  -H "Authorization: Bearer $HUBZOID_BRIDGE_KEY" \
  -H "Content-Type: text/csv" \
  --data-binary @count-sheet.csv

Inline attachments are announced to the agent automatically. Images are shown to the model and other files are read with read_upload. A file over HUBZOID_MAX_UPLOAD_BYTES (25 MiB by default) returns 413 and the request is refused as a whole. Files the agent saves with write_artifact come back as signed links in the answer and can also be fetched from /artifacts/{chat_id}/{filename} with the bridge key.

Identity headers

The bridge trusts these headers because only holders of the bridge key can reach it. The public port strips them from outside requests.

Prop

Type

Without identity headers a request runs without a verified identity: unrestricted tools and knowledge only, no restricted tools. Once a hub's access is managed in the Console, the bridge requires a verified identity that holds Use this agent:

SituationResponse
No X-OpenWebUI-User-Email or X-Hubzoid-User403 The hub requires sign-in.
The identity lacks Use this agent403 You do not have access to the hub.
The person is blocked in the Console403 Their agent access is blocked.

Set X-Hubzoid-Surface: api on your own calls. api may reach restricted tools like the chat app, and usage is then recorded under the api channel instead of web.

Next steps

Read this page as Markdown

Choose which cookies Hubzoid can use. You can change this at any time from Cookie settings in the footer. Read the Cookie Policy for details.