Build anything on Cube Exchange.
Cube Exchange is a live central-limit order-book exchange. This reference documents the complete unified REST API for programmatic trading, market making and data — one consistent set of endpoints, one symbol format, one response shape per resource. If you have written a bot for any major exchange, this will feel immediately familiar.
https://commodities.gocube.org/api/v2/commodity/v1Quick start.
Every market-data endpoint is public. Fetch the markets, then a book and candles:
# 1. all markets
curl https://commodities.gocube.org/api/v2/commodity/v1/markets
# 2. order book + candles
curl "https://commodities.gocube.org/api/v2/commodity/v1/orderbook?symbol=USDT/NGN"
curl "https://commodities.gocube.org/api/v2/commodity/v1/ohlcv?symbol=USDT/NGN&timeframe=1h&limit=100"To trade, generate an API key and send it as X-API-Key. That’s the whole model — no request signing, no nonces.
Conventions.
| Concept | Rule |
|---|---|
symbol | Unified BASE/QUOTE, uppercase, e.g. USDT/NGN. The same symbol is used everywhere — market data and trading. List them from /markets. |
Numbers | Prices, sizes and amounts are decimal strings to preserve precision; parse with a decimal library. |
Timestamps | Unix milliseconds throughout, each paired with an ISO-8601 datetime. OHLCV bucket times are the millisecond open time. |
Base / quote | Base = the traded asset; quote = the settlement currency. Price is quote per 1 base; cost is in the quote currency. |
Fees | A flat 0.15% trading fee applies to every fill, maker and taker. |
Errors | Non-2xx responses return { "error": "message", "status": 4xx }. See the error reference. |
Enumerations.
| Field | Values | Notes |
|---|---|---|
side | buy · sell | Order direction (lowercase). |
type | limit · market | Limit orders require price; market orders omit it. |
status | open · closed · canceled · rejected | open = resting, closed = fully filled. |
timeInForce | GTC | Good-till-cancel (resting limit orders). |
takerOrMaker | taker · maker | Your role in a fill (on My trades). |
timeframe | 1m 5m 15m 1h 4h 1d | OHLCV bucket sizes. |
Authentication.
Market data is public — no key required. Trading requires an API key issued to your Cube Exchange (Cubemail) account. Send it on every trading request:
X-API-Key: cdx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxAPI keys.
Manage keys from your account or the generator below. Programmatic key management is also available with an existing session:
/keysIssue a new key. Body { "label": "my-bot" }. Returns the plaintext key once plus its id and prefix.
/keysList your keys (prefixes and labels only — never the secret).
/keys/:id/revokePermanently revoke a key.
Sign in with your Cubemail account to generate an API key.
Sign inRate limits.
Limits are generous and designed for real-time bots and minutely aggregator polling.
| Surface | Guidance |
|---|---|
| Market data | Poll as often as once per second per endpoint. Books and candles update continuously; there is no benefit to tighter polling. |
| Data polling | Minutely snapshot polling of market data is fully supported; no key or sign-up required. |
| Trading | Sustained order placement/cancel is fine for market making. Burst-cancel-and-replace loops of a few seconds are the intended pattern. |
| Abuse | Excessive traffic may be throttled per key/IP. If you need higher throughput, contact the team. |
Markets & currencies.
/v1/marketsEvery listed market — the canonical trading list. Returns an array of market structures.
[
{ "id": "usdtngn", "symbol": "USDT/NGN", "base": "USDT", "quote": "NGN",
"active": true, "type": "spot", "spot": true,
"taker": 0.0015, "maker": 0.0015,
"precision": { "amount": 8, "price": 8 },
"limits": { "amount": { "min": "1" } } }
]/v1/currenciesEvery listed asset, keyed by code — { "USDT": {…}, "NGN": {…} }.
/v1/timeServer time — { "timestamp": 1720…, "datetime": "…" }.
/v1/statusExchange status — { "status": "ok", "updated": … }.
Tickers.
/v1/ticker?symbol=USDT/NGNLive statistics for one market. Returns a ticker structure — last, bid, ask, 24h high/low and base/quote volume.
/v1/tickersAll tickers at once, as a map of { "BASE/QUOTE": ticker }.
Order book.
/v1/orderbook?symbol=USDT/NGN&limit=50Aggregated depth — bids high→low, asks low→high, each level [price, amount].
{
"symbol": "USDT/NGN",
"bids": [ ["1372.10", "42.5"], ["1372.05", "18.0"] ],
"asks": [ ["1372.40", "31.2"], ["1372.55", "12.8"] ],
"timestamp": 1720008000000, "datetime": "2026-07-03T12:00:00.000Z"
}OHLCV.
/v1/ohlcvCandlesticks aggregated from trades — the charting feed.
Query parameters
| Param | Type | Req | Description |
|---|---|---|---|
symbol | string | yes | Unified symbol, e.g. USDT/NGN. |
timeframe | enum | no | Bucket size (default 1h). |
since | int | no | Start time, unix ms. |
limit | int | no | Max candles. |
[
[ 1720008000000, "1372.1", "1372.4", "1371.6", "1372.9", "142.5" ]
] // [ time_ms, open, high, low, close, volume ]Trades.
/v1/trades?symbol=USDT/NGN&limit=50Recent public trades — the tape. Each is a trade structure (id, timestamp, price, amount, cost, side, takerOrMaker).
Aggregator data feed.
A public, keyless market-data feed in the industry-standard exchange format consumed by data vendors, aggregators and market terminals — pair-level tickers, full depth and the historical tape. Pair ids here are uppercase BASE_TARGET (e.g. USDT_NGN), and all values are decimal strings.
https://commodities.gocube.org/api/v2/commodity/feed/feed/tickersEvery trading pair with 24h statistics — the summary feed vendors poll on a schedule.
| Field | Type | Description |
|---|---|---|
ticker_id | string | Pair id, BASE_TARGET. |
base_currency / target_currency | string | Base and quote codes. |
last_price | string | Last trade price. |
base_volume / target_volume | string | 24h volume in base / quote. |
bid / ask | string | Best bid / ask. |
high / low | string | 24h high / low. |
[
{ "ticker_id": "USDT_NGN", "base_currency": "USDT", "target_currency": "NGN",
"last_price": "1372.19", "base_volume": "1.04", "target_volume": "1439.83",
"bid": "1370.0", "ask": "1374.0", "high": "1398.5", "low": "1360.4" }
]/feed/orderbook?ticker_id=USDT_NGN&depth=100Full order-book depth for one pair. depth caps the levels returned per side.
{ "ticker_id": "USDT_NGN", "timestamp": 1720008000000,
"bids": [ ["1370.0", "42.5"] ], "asks": [ ["1374.0", "31.2"] ] }/feed/historical_trades?ticker_id=USDT_NGN&type=&limit=100The completed-trade tape, split into buy and sell arrays. Filter with type=buy or type=sell.
{ "buy": [ { "trade_id": 961247, "price": "1372.19", "base_volume": "0.0001",
"target_volume": "0.134", "trade_timestamp": 1720008000000, "type": "buy" } ],
"sell": [ … ] }WebSocket feed.
Stream the market in realtime over a single connection instead of polling. One socket carries three channels per symbol — the live trade tape, ticker updates and order-book snapshots — pushed as JSON text frames. No key required; the feed is public and read-only.
wss://commodities.gocube.org/api/v2/commodity/v1/wsSubscribe by sending a JSON frame naming the channels you want as channel:SYMBOL (unified BASE/QUOTE). The server acknowledges, then streams matching frames. Send ping to keep the link warm.
Channels
| Channel | Pushed | Payload |
|---|---|---|
trades | As trades print | Array of trade structures — new fills since the last frame. |
ticker | ~1s snapshot | A ticker structure — last, bid/ask, 24h stats. |
book | ~1s snapshot | Order book — { bids, asks, timestamp }, each level [price, amount]. |
Client → server
{ "method": "subscribe", "params": ["trades:USDT/NGN", "ticker:USDT/NGN", "book:USDT/NGN"] }
{ "method": "unsubscribe", "params": ["book:USDT/NGN"] }
{ "method": "ping" }Server → client
{ "event": "welcome", "channels": ["trades","ticker","book"] }
{ "event": "subscribed", "channels": ["trades:USDT/NGN", …] }
{ "channel": "trades", "symbol": "USDT/NGN", "data": [ { "id": "961247", "price": "1372.19", "amount": "0.0001", "side": "buy", "timestamp": 1720008000000 } ] }
{ "channel": "ticker", "symbol": "USDT/NGN", "data": { "last": "1372.19", "bid": "1370.0", "ask": "1374.0", "high": "1398.5", "low": "1360.4", "baseVolume": "1.04" } }
{ "channel": "book", "symbol": "USDT/NGN", "data": { "bids": [["1370.0","42.5"]], "asks": [["1374.0","31.2"]], "timestamp": 1720008000000 } }
{ "event": "pong" }Connect
# quick test from a terminal
wscat -c "wss://commodities.gocube.org/api/v2/commodity/v1/ws"
> {"method":"subscribe","params":["trades:USDT/NGN","ticker:USDT/NGN","book:USDT/NGN"]}The socket also answers at /ws and /feed/ws under the same host. Trades stream only new prints from the moment you subscribe — seed history from /v1/trades. Reconnect and re-subscribe on disconnect; snapshots make the book self-healing.
Charting & OHLCV.
Build a charting datafeed from /v1/ohlcv: request candles by symbol and timeframe, and page through history with since (unix ms) and limit. Supported timeframes are 1m · 5m · 15m · 1h · 4h · 1d. Each row is [ time_ms, open, high, low, close, volume ], oldest first — ready to map onto any candlestick widget. Pair time-series with /v1/trades for the live tape and /v1/ticker for the header stats.
# last 500 hourly candles for USDT/NGN
curl "https://commodities.gocube.org/api/v2/commodity/v1/ohlcv?symbol=USDT/NGN&timeframe=1h&limit=500"
# a historical window (since = unix ms)
curl "https://commodities.gocube.org/api/v2/commodity/v1/ohlcv?symbol=USDT/NGN&timeframe=1d&since=1717200000000&limit=90"Balance.
/v1/balanceYour funds as free, used and total per currency (a balance structure).
{
"USDT": { "free": "1200.00", "used": "300.00", "total": "1500.00" },
"NGN": { "free": "540000", "used": "0", "total": "540000" },
"free": { "USDT": "1200.00", "NGN": "540000" }, "used": { … }, "total": { … }
}Orders.
/v1/orderPlace an order.
Body
| Field | Type | Req | Description |
|---|---|---|---|
symbol | string | yes | Unified symbol, e.g. USDT/NGN. |
type | enum | yes | limit or market. |
side | enum | yes | buy or sell. |
amount | string | yes | Order size in base units. |
price | string | limit | Limit price (quote per base). |
curl -X POST https://commodities.gocube.org/api/v2/commodity/v1/order \
-H "X-API-Key: $KEY" -H "content-type: application/json" \
-d '{"symbol":"USDT/NGN","type":"limit","side":"buy","amount":"100","price":"1370"}'Response 201 — an order structure
{ "id": "5231", "symbol": "USDT/NGN", "type": "limit", "side": "buy",
"price": "1370", "amount": "100", "filled": "0", "remaining": "100",
"status": "open", "timeInForce": "GTC", "timestamp": 1720008000000 }/v1/order/:id/cancelCancel a resting order (also DELETE /v1/order/:id). Returns the canceled order.
/v1/order/:idFetch one order by id.
/v1/orders/openYour open (resting) orders. Also /v1/orders (all) and /v1/orders/closed (filled). Optional ?symbol=.
My trades.
/v1/mytrades?symbol=USDT/NGNYour fills with fee and takerOrMaker. Optional ?symbol= and ?limit=.
Response structures.
Every resource returns one consistent shape. Prices, sizes and amounts are decimal strings; timestamps are unix milliseconds paired with an ISO-8601 datetime; every structure carries a raw info object with the underlying exchange payload.
Market
| Field | Type | Description |
|---|---|---|
id | string | Exchange market id. |
symbol | string | Unified BASE/QUOTE. |
base / quote | string | Asset codes. |
baseId / quoteId | string | Exchange-native asset ids. |
active | bool | Tradeable now. |
type · spot · margin | string · bool | Market class and capabilities. |
taker / maker | number | Fee rates (0.0015 = 0.15%). |
precision | object | { amount, price } decimal places. |
limits | object | { amount, price, cost, leverage } min/max. |
info | object | Raw exchange payload. |
Ticker
| Field | Type | Description |
|---|---|---|
symbol | string | Unified symbol. |
timestamp / datetime | int / string | ms + ISO-8601. |
high / low | string | 24h high / low. |
bid / ask | string | Best bid / ask. |
last / close | string | Last trade price. |
baseVolume / quoteVolume | string | 24h volume in base / quote. |
Order book
{ symbol, bids: [[price, amount], …], asks: [[price, amount], …], timestamp, datetime } — bids sorted high→low, asks low→high.
OHLCV
An array of arrays, oldest first: [ timestamp_ms, open, high, low, close, volume ].
Trade
| Field | Type | Description |
|---|---|---|
id | string | Trade id. |
timestamp / datetime | int / string | Fill time. |
symbol | string | Unified symbol. |
order | string | Your order id (on My trades). |
side | string | buy / sell. |
takerOrMaker | string | taker / maker. |
price / amount / cost | string | Fill price, base size, and price×amount (quote). |
fee | object | { cost, currency } when known. |
Order
| Field | Type | Description |
|---|---|---|
id | string | Order id. |
clientOrderId | string | Client-side id (uuid). |
timestamp / datetime | int / string | Placement time. |
lastTradeTimestamp | int | Time of the last fill. |
symbol | string | Unified symbol. |
type | string | limit / market. |
timeInForce | string | GTC. |
side | string | buy / sell. |
price | string | Limit price. |
amount / filled / remaining | string | Original, filled and remaining base amount. |
cost | string | Filled cost in the quote currency. |
status | string | open · closed · canceled · rejected. |
Balance
A map of { CODE: { free, used, total } } plus flat free, used and total maps. free is available, used is locked in open orders, total = free + used.
Currency
{ id, code, name, active, deposit, withdraw, precision }, keyed by uppercase code.
Build a market-making bot.
A minimal two-sided quoter — read the reference price, lay a bid and an ask around it, and cancel-and-replace on a loop so your book is always fresh. Size every quote from your real free balance so you never over-commit.
const B = "https://commodities.gocube.org/api/v2/commodity/v1";
const H = { "content-type": "application/json", "X-API-Key": process.env.API_KEY };
const SYMBOL = "USDT/NGN", SPREAD = 0.002, SIZE = "50"; // 20 bps each side
const j = (u, o) => fetch(B + u, o).then(r => r.json());
async function tick() {
const t = await j("/ticker?symbol=" + encodeURIComponent(SYMBOL));
const ref = (parseFloat(t.bid) + parseFloat(t.ask)) / 2 || parseFloat(t.last);
if (!ref) return;
const open = await j("/orders/open", { headers: H }); // clear our quotes
await Promise.all(open.map(o =>
fetch(`${B}/order/${o.id}/cancel`, { method: "POST", headers: H })));
const bid = (ref * (1 - SPREAD)).toFixed(2); // re-quote both sides
const ask = (ref * (1 + SPREAD)).toFixed(2);
for (const [side, price] of [["buy", bid], ["sell", ask]])
await fetch(B + "/order", { method: "POST", headers: H,
body: JSON.stringify({ symbol: SYMBOL, type: "limit", side, amount: SIZE, price }) });
console.log("quoted", bid, "/", ask);
}
setInterval(tick, 5000);Harden it with multiple ladder levels, inventory skew, and balance-capped sizing — never quote more than your free balance. Use /ohlcv for signals and /balance to track inventory.
Error reference.
Errors return a non-2xx HTTP status with a body of { "error": "message", "status": 4xx }. Each maps to a typed exception so you can catch by kind rather than parse strings.
| HTTP | Exception | When |
|---|---|---|
| 400 | BadRequest | Malformed request body or parameters. |
| 401 | AuthenticationError | Missing or invalid X-API-Key. |
| 403 | PermissionDenied | The key lacks permission for this action. |
| 404 | BadSymbol · OrderNotFound | No such market or order. |
| 422 | InvalidOrder | Bad price/size, below minimum, or price out of band. |
| 422 | InsufficientFunds | Not enough free balance to place the order. |
| 429 | RateLimitExceeded | Slow down; retry after a short backoff. |
| 5xx | ExchangeNotAvailable | Matching engine offline or a transient outage — retry. |
Changelog.
The API is versioned in the path (/v1). Additive changes ship without a version bump; breaking changes get a new version.