API reference

The signed request contract, every scope and which endpoint needs it, the public and bot-facing REST surface, the WebSocket streams and their handshakes, the admin endpoints with their permissions, and the wire dialect that catches integrators out.

5 min readUpdated 3 August 2026api, hmac, scopes, websocket, endpoints, permissions

Three surfaces, three authentication models. /api/hb/* bot paths are HMAC-signed. A carve-out of /api/hb/* browser paths use the ordinary session. Everything under /api/admin/hb/* is a session plus an explicit permission.

The convention that will catch you out

The platform normally pins HTTP at 200 and puts the outcome in the body. The Hummingbot bot-facing endpoints deliberately do the opposite: they speak the Binance dialect, with real status codes and a {code, msg} error envelope, and they skip the CORS middleware that would otherwise lock the status line at 200. That is what lets a genuine 429 or 423 reach a bot's error parser.

The browser-facing carve-out — /api/hb/keys, /api/hb/strategy, /api/hb/setup, /api/hb/console and /api/hb/connector, with everything under them — keeps the platform dialect ({message, statusCode}), because $fetch cannot read a Binance envelope. Which dialect you get is decided by the path, not by the caller.

Signing a request

Four headers on every signed call:

Header Value
X-API-Key The public key string
X-Timestamp Unix milliseconds
X-Nonce 16–128 hex characters, single-use
X-Signature Hex HMAC-SHA256 of the payload below
METHOD\npath\ntimestamp\nnonce\nrawBody

path is the URL pathname only — no query string. Query parameters are already part of the request line, and keeping them out of the payload keeps composition stable across proxies that rewrite trailing slashes. rawBody is the empty string when there is no body.

payload = f"{method.upper()}\n{path}\n{ts}\n{nonce}\n{body or ''}"
sig = hmac.new(secret.encode(), payload.encode(), hashlib.sha256).hexdigest()

Rules the server enforces, in this order:

  1. Timestamp sanity. Anything more than five minutes out is refused before the key is looked up at all, so probe traffic never reaches account records.
  2. Key lookup, then signature verification. Everything below this point is reachable only by a caller who holds the secret — which is why an attacker who merely knows a public key string cannot probe its state or drive audit writes.
  3. Expiry → 401. Disabled423 Locked, carrying the operator's or the customer's own reason as the message.
  4. Receive window → 401, naming the direction and size of the drift. Default ±10 seconds.
  5. IP allowlist, when the key has one. Deny by default: a request whose source IP cannot be determined, or a key whose allowlist is empty, is refused.
  6. Replay. Nonces are held in Redis for twice the receive window; a repeat is 401 Replayed nonce.

Scopes

Scope Authorises
hb:read:account Balances, order lookups, order lists
hb:read:market Signed market-data reads
hb:trade:spot Placing spot orders
hb:cancel:spot Cancelling and amending spot orders
hb:trade:perp Placing perpetual orders
hb:cancel:perp Cancelling and amending perpetual orders
hb:perp:positions Reading positions and setting leverage
hb:control:bot Driving the customer's own bot through the local agent

Presets offered on the key form: read-only, spot-trade, perp-trade, bot-control, full-access.

Every other scope authorises an action on your exchange. This one authorises an action on their machine, so it is the only scope whose blast radius is outside the platform — which is why it is absent from both trading presets. Existing keys never gain it: permissions are stored as a literal array at creation.

Read-only mode blocks any scope that does not begin hb:read:.

Public endpoints

No authentication. Metered per client IP against the market-data budget.

Liveness. The first thing to curl when anything is unclear
Server time in unix milliseconds — compare it against the bot host's clock
Spot symbols with Binance-shaped filters and precision
24h ticker, Binance-shaped
Spot order book snapshot
Recent public trades
Perpetual symbols, Binance-Futures-shaped
Perpetual order book snapshot
Current funding rate

Symbols are dash-separated on the wire (BTC-USDT); the platform stores them as separate base and quote columns and renders BTC/USDT internally. Asking for more depth than a market has returns the available levels rather than an error, so a bot's initial book is never left empty by a depth request.

Signed spot endpoints

Account balances — ECO and FUTURES wallets, Binance-shaped
Place a spot order. Forwarded to the Ecosystem engine
List orders for a symbol, newest first — open and closed
Batch-cancel open spot orders
One order by id
Amend an order — cancel and replace
Cancel one order by id

An earlier build returned everything except the live ones, so a bot reasonably concluded its book was empty and re-quoted on top of its own resting orders. The list now returns open and closed, newest first. A request that asks explicitly for open or closed is unaffected.

Signed perpetual endpoints

Place a perpetual order. Forwarded to the Futures engine
Batch-cancel open perpetual orders
One perpetual order by id
Amend a perpetual order
Cancel one perpetual order
Open positions
Set leverage for a symbol

Leverage is validated against what the market offers, so a rejection reads leverage 7x is not offered on BTC-USDT. Available: 1, 5, 10 rather than an opaque refusal on every subsequent order.

This addon owns no book. With Ecosystem absent every spot path returns a clean 503; with Futures absent every perpetual path does. The addon's own endpoints — ping, time, keys, strategy presets — keep working either way.

Rate-limit buckets

Bucket Governs
trade Order placement, cancellation, amendment, batch cancel
account Balances, order lookups, order lists, positions, leverage
read Public market data

Every response carries X-RateLimit-Bucket, X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; a refusal adds Retry-After. Defaults and overrides are covered in Settings.

Browser endpoints

Session-authenticated, platform error dialect, scoped to the caller's own account. No permission — access is gated by the view_hb KYC feature where noted.

List the caller's Hummingbot-capable keys
Create a key and mint a secret. The secret is returned ONLY here
Update a key — name, scopes, IP restriction, expiry
Delete a key
Rotate the signing secret. The old one dies immediately
Disable a key — the customer's own kill switch
Re-enable a key the customer disabled
That key's audit trail
Setup state: base URL, connector names, key state and whether a bot is reaching us
Download the connector kit as a zip. Contains no credentials
One-shot trading snapshot for the console. Optional ?symbol=
Emergency stop: disable keys, then cancel every open order
Published strategy presets
Render one published preset to controller YAML

Creating and rotating a key are additionally subject to hbRequireKyc. The connector kit, the console, the setup state and both strategy endpoints are gated by the view_hb KYC feature.

POST /api/hb/console/panic takes { cancelOrders?, disableKeys?, reason? }, both booleans defaulting to true. Passing both as false is a 422 — there would be nothing to do.

WebSocket

/api/hb/stream — the connector's feed

One socket, multiplexed. Public streams need no authentication; private ones require an in-band signed handshake.

{ "action": "SUBSCRIBE", "params": ["orderbook@BTC-USDT", "trades@BTC-USDT"] }
{ "action": "AUTH", "apiKey": "…", "timestamp": 1750000000000, "nonce": "…", "signature": "…" }

The signature covers WS\n/api/hb/stream\n<timestamp>\n<nonce>\n. On success the verified user id is bound to the connection and only that user's data is delivered. A ?userId= query parameter is never trusted for identity.

Stream Carries
orderbook@SYM, depth@SYM Spot book snapshots and diffs, from the Ecosystem book only
trades@SYM, ticker@SYM Public spot trades and ticker
perpOrderbook@SYM, markPrice@SYM, fundingRate@SYM Perpetual, from the Futures book only
userOrders, userBalances, userPositions, userTrades Private, after AUTH

Spot and perpetual books are separate channels on purpose. A shared channel that fell back from the ecosystem book to the perpetual one meant a perp connector tracking a symbol that also trades spot silently quoted off spot liquidity.

Server frames are { "stream": "...", "data": { ... } }. Both common keepalive conventions are answered.

/api/hb/console — the browser terminal

Session-authenticated. Same path as the REST snapshot, registered separately by method. Pushes the same shape the one-shot endpoint returns.

/api/hb/agent — the local agent

Signed handshake over WS\n/api/hb/agent\n<timestamp>\n<nonce>\n, and the key must additionally carry hb:control:bot.

Direction Frames
Agent → server AUTH, HELLO, HEARTBEAT (~25s), TELEMETRY, RESULT
Server → agent authAck, command, error

Agent presence is a Redis key with a 90-second TTL refreshed by the heartbeat, and commands travel over Redis pub/sub with a local fast path — so a browser and an agent held by different backend threads still reach each other. An agent handshake deliberately does not stamp the key's "last used" field: that field is the only signal for whether a bot is reaching the exchange, and an agent is not a bot.

Admin endpoints

Every Hummingbot-capable key on the platform
Fleet counts plus a rolling 24h authentication-failure figure
One key's audit trail
Disable any key. The reason is what the bot is told
Re-enable a key
Set or clear a per-key rate-limit override
Command-center controls: kill switch, read-only, key cap
Every account running a Hummingbot bot
One account's live orders and positions
Force-cancel all of that account's orders, spot and perpetual
Flatten: cancel the orders and close every open position
List supervised instances
Register an instance. Creates the row STOPPED — nothing starts
Detect Hummingbot checkouts and interpreters on this host
Diagnose an install: nine checks, each pass, warn or fail
Prepare a location, create it, or clone Hummingbot into it
Markets an instance can quote, spot and perpetual separately
Keys this operator can attach to an instance
Mint a scoped key for one of this operator's bots
Update an instance. Config is read at launch, so restart to apply
Delete an instance. Refused on a running one without confirmation
Start — writes desired state; the supervisor converges
Stop — SIGTERM, then 20 seconds before it is forced
Restart — regenerates the controller YAML from the preset
Read that instance's captured logs
Reinstall the shipped connector into that checkout
List strategy presets
Create a preset. Validated on save and again on export
Update a preset
Delete a preset
Bulk delete presets
Render a preset to controller YAML

There is also an admin WebSocket at /api/admin/hb/instance carrying live instance state and log lines, gated on view.hb.instance.

Settings are read and written through the platform's own endpoints (/api/admin/system/settings), not through an addon route, which is why saving the Hummingbot settings screen needs edit.settings.

Tables

Table Holds
hb_strategy_preset Presets: family, pair, connectors, config JSON, status, version
hb_instance Server-run bots: paths, preset and key links, desired and actual status, restart counters, last exit code and error
api_key The keys themselves. A Hummingbot key is one carrying an hb:* scope
api_key_audit_log Creation, rotation, permission changes, kill-switch events and authentication rejections

These are the names to use in SQL. The backend refers to the last two by their model names, apiKey and apiKeyAuditLog, which is what appears in logs and stack traces — but neither is a table a query can name.

config, permissions, ipWhitelist and rateLimitOverride are JSON columns. MySQL hands them back parsed; MariaDB hands them back as raw strings. The backend normalises all of them, but a direct database consumer must handle either — and must not treat the raw string as a list. An IP allowlist compared as text degrades into a substring match, which is how a partial address once satisfied it.