Call The Gateway
A task guide for app developers — from zero to a free hosted chat completion served by your own machines on the B3IQ gateway, including listing models, errors, and streaming.
This guide walks an app developer from nothing to a first chat completion on the B3IQ hosted gateway. The gateway is OpenAI-compatible, so most existing clients work with a base URL and a key swap.
Use https://b3iq.org/v1/api as the gateway base URL for application traffic.
Hosted inference is free — your keys route to the machines on your own
account under B3IQ router policy.
Skip the raw HTTP below — the OpenAI SDK Quickstart
covers the same flow as a base_url/api_key swap in Python or JavaScript.
From Zero To A First Call
Create an account and a gateway key
Sign in to the dashboard and create a gateway key. Hosted keys are
b3iq_gateway_ secrets — they are shown once, stored hashed, and revocable.
Save the value to a file you control rather than pasting it into scripts.
Have a machine on your account
Your keys route only to machines on your own account, so you need at least one online machine serving the model you want to call. See Run your first node to bring one up, and Usage & Quotas for how free usage is metered.
List the live models
Read GET /v1/api/models with your key. The response is exactly the models
the machines on your account serve right now — the set changes as your
machines come and go.
Make your first chat call
Send POST /v1/api/chat/completions with a model id from the list and your
gateway key. The request fails closed if no machine on your account can
serve it or it is not allowed by policy.
Handle errors and add streaming
Read the stable machine code on any denial, then set stream: true once the
non-streaming call works.
Store Your Key In A File
Keep the secret out of argv and shell history. Write it to a file, lock the permissions, and read it from there.
bashumask 077printf '%s\n' 'b3iq_gateway_...' > "$HOME/.b3iq/gateway-key"chmod 600 "$HOME/.b3iq/gateway-key"
Process arguments are visible to other processes and land in shell history.
Read b3iq_gateway_ secrets from a chmod 600 file or an environment
variable, and pass them to curl through a temporary config file — never as a
literal -H flag on the command line.
The examples below expect two variables:
bashexport B3IQ_GATEWAY_BASE_URL="https://b3iq.org/v1/api"export B3IQ_GATEWAY_KEY_FILE="$HOME/.b3iq/gateway-key"
List Models First
Send your key with the list call. With a key, the response is not a network catalog — it is your machines' models, so an id you see is an id you can call.
bash: "${B3IQ_GATEWAY_BASE_URL:?set B3IQ_GATEWAY_BASE_URL}": "${B3IQ_GATEWAY_KEY_FILE:?set B3IQ_GATEWAY_KEY_FILE}"cfg="$(mktemp)"chmod 600 "$cfg"trap 'rm -f "$cfg"' EXIT{ printf 'url = "%s/models"\n' "${B3IQ_GATEWAY_BASE_URL%/}" printf 'header = "Authorization: Bearer %s"\n' "$(sed -n '1p' "$B3IQ_GATEWAY_KEY_FILE")"} > "$cfg"curl --fail --silent --show-error --config "$cfg"
The model field takes an id returned by GET /v1/api/models. Sent with
your gateway key, that list is exactly the models the machines on your
account serve right now — machines you've switched to Earn mode are
excluded, and any node scope on the key is respected. The chat example below
uses a representative id (qwen3-8b); always list the live models first,
since the set changes as your machines come and go. Without a key the
endpoint returns the public network-wide catalog instead, which says nothing
about what your account can route to.
The b3iq CLI makes the same call for you: install it, run
b3iq login, then b3iq models list to see what your keys can route to
before writing any application code.
First Chat Completion
bash: "${B3IQ_GATEWAY_BASE_URL:?set B3IQ_GATEWAY_BASE_URL}": "${B3IQ_GATEWAY_KEY_FILE:?set B3IQ_GATEWAY_KEY_FILE}"cfg="$(mktemp)"chmod 600 "$cfg"trap 'rm -f "$cfg"' EXIT{ printf 'url = "%s/chat/completions"\n' "${B3IQ_GATEWAY_BASE_URL%/}" printf 'request = "POST"\n' printf 'header = "Authorization: Bearer %s"\n' "$(sed -n '1p' "$B3IQ_GATEWAY_KEY_FILE")" printf 'header = "Content-Type: application/json"\n'} > "$cfg"curl --fail --silent --show-error --config "$cfg" --data-binary @- <<'JSON'{ "model": "qwen3-8b", "messages": [{ "role": "user", "content": "Say hello from B3IQ." }], "max_tokens": 64}JSON
javascriptimport { readFileSync } from "node:fs";const baseURL = process.env.B3IQ_GATEWAY_BASE_URL;const token = readFileSync(process.env.B3IQ_GATEWAY_KEY_FILE, "utf8").trim();const response = await fetch(`${baseURL}/chat/completions`, { method: "POST", headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }, body: JSON.stringify({ model: "qwen3-8b", messages: [{ role: "user", content: "Say hello from B3IQ." }], max_tokens: 64 })});if (!response.ok) { console.error(response.status, await response.text());} else { console.log(await response.json());}
A successful response is an OpenAI-style chat completion, served free by a
machine on your account. The X-B3IQ-Router-Node-ID response header tells you
which one served it.
Handle Errors
Hosted chat fails closed rather than routing somewhere you did not intend. Common denials and what they mean:
no_eligible_nodes— no machine on your account serves the requested model right now. If you listed models with your key first, this mostly means a machine went offline in between; it is the expected failure only when the id came from the anonymous network-wide list.- Out-of-policy model or route tier.
- Per-key or per-account rate limits exceeded.
account_token_quota_exceeded— the account's daily token quota is exhausted (the default quota is uncapped).
Every denial carries a stable machine code in an OpenAI-style error object.
Branch on the code, not the human-readable message. See
Errors for the full list.
Streaming
Set stream: true to receive Server-Sent Events. The gateway proxies the
selected node stream and still parses final usage and receipt metadata, so
streamed requests are metered the same as non-streamed ones.
json{ "model": "qwen3-8b", "messages": [{ "role": "user", "content": "Stream a short response." }], "stream": true}
Get the non-streaming call working first, then flip stream on — error
envelopes and metering behavior are otherwise identical.

