Yellow BoxTrading APIv1 · pre-release

Change Log

Newest first. Backward-compatibility rules: general-info.md § Versioning.

Changes that only add a field, an enum value, an endpoint or a stream are listed here but are not breaking — your client must already tolerate them.

v1.0.0 (draft) — documentation verified against the implementation, 2026-10-08

No behaviour changed; the documentation was corrected to match what the service does.

  • New page: Known limitations (pre-release) — the first session against the new trade server: no dealer answer (non-market operations stay ACCEPTED / NEW), origClientOrderId does not resolve a pending order for modify / cancel / query, cancels fail on the server, GET /v1/userTrades is empty on staging, and the server reports a UTC+3 offset.
  • DELETE /v1/order takes newClientOrderId (idempotency key for the cancel); its response carries clientOrderId and side. PUT /v1/position's response carries clientOrderId.
  • DELETE /v1/position has no profit field — the realised profit is on the DEAL event (rp) and GET /v1/userTrades.
  • Order responses omit sl, tp and expiration when none is set (they were shown as "0" / 0). GET /v1/order and GET /v1/openOrders still always carry them.
  • A pending order is NEW from the moment it is queued, including on ACK; PUT /v1/order, DELETE /v1/order and PUT /v1/position always wait up to 5 s like RESULT.
  • fillModes never contains RETURN. Unmapped MT5 values appear as UNKNOWN_<n>.
  • marginLevel is "0.00" (two decimals) with no open position.
  • Error details documented: -1106 for timeInForce on MARKET and for a stray stopLimitPrice, -1130 for a malformed timestamp / recvWindow, -1128 for endTime before startTime, batch envelope errors (-1130, -1131, -1132), WebSocket control-frame codes and close codes. Codes in the catalogue that are not currently returned are listed under error-codes.md § Reserved codes.

v1.0.0 (draft) — second hardening pass, 2026-09-24

Still draft and pre-launch; listed because each is observable.

  • -1133 on POST /v1/order and on each POST /v1/batchOrders item. A newClientOrderId your key already used for a different kind of operation — or for a different login — is refused (in a batch, as the error object in that item's slot). The login rule now applies to every endpoint that takes newClientOrderId, including DELETE /v1/allOpenPositions.
  • A retried POST /v1/order is answered from the stored record before any validation. A retried limit order whose price the market has since crossed returns its stored status instead of -2021.
  • An internal fault in the trade executor is reported as IN_DOUBT (-5009), never as a retryable -1000: the order may have been sent. Retry only with the same newClientOrderId.
  • Orders that could not be queued within about a minute are expired, not sent late. They read REJECTED with -1000 "Service is currently unavailable" on GET /v1/order; nothing executed. Retrying the same newClientOrderId sends it as a fresh order. If such an order did reach the trade server after all, its fill still lands and the record changes to FILLED.
  • A REJECTED order can still become FILLED when its rejection was the execution path's own refusal (no mt5RetCode) and a later attempt under the same newClientOrderId executed. A rejection carrying an mt5RetCode is final, as before.
  • POST /v1/batchOrders answers -5015 when the execution path is not running, before any item is queued or charged — like every other TRADE endpoint. A trade executor that stopped reading its queue is now detected within about 30 seconds.
  • DELETE /v1/allOpenPositions retries complete an interrupted sweep. A position in the original snapshot whose close was never queued is closed on the retry if it is still open, and reported REJECTED (-5006) if it has closed since. A concurrent identical sweep returns the first sweep's snapshot.
  • C (clientOrderId) on DEAL / ORDER_UPDATE is only published to the key that owns it. Where two keys share a login, the other key receives the event without C.
  • GET /v1/accounts and the user data stream list exactly the logins the REST endpoints accept. An archived account is no longer listed or streamed.
  • Market-stream connections are capped at 20 concurrent per client IP (and by overall capacity); an excess connection is refused before the upgrade with -1008.
  • A user data stream is also closed (1011) when the server's own intake falls behind for your logins, not only when your connection reads too slowly.

v1.0.0 (draft) — clarifications, 2026-09-19

Still draft, still pre-launch, so none of this is a breaking change to a live client. Listed because each one is behaviour an integrator can observe.

  • PARTIALLY_FILLED is now a market-execution status. POST /v1/order with type=MARKET, DELETE /v1/position and each DELETE /v1/allOpenPositions leg can report it. executedVolume is the sum of the deals that have filled the order and price their volume-weighted average; the status becomes FILLED when the sum reaches the requested volume. Previously a partial fill was reported as FILLED with the volume you requested.
  • DELETE /v1/allOpenPositions retries replay the original snapshot. Reusing a newClientOrderId returns the first sweep's legs and closes nothing opened since — including after a sweep that found nothing, which stays requested: 0. A fresh sweep needs a fresh id.
  • PUT /v1/order: timeInForce is conditionally mandatory. It is required when the service cannot determine the order's current expiration mode — typically an order this API did not place. -1102 rather than a guess, because a guess would rewrite a DAY order as GTC.
  • Retries of PUT/DELETE /v1/order, PUT/DELETE /v1/position are answered from the stored record, not re-validated: a retried close whose position has since closed returns its stored FILLED outcome instead of -2023, and a retried modify returns its stored outcome instead of -5005.
  • A failure to queue an order answers -5009 (execution status UNKNOWN), not -1000: the write may have landed before the error surfaced, so the order is recorded as still open and can still fill. Retry it identically, same newClientOrderId.
  • TRADE endpoints answer -5015 when the execution path is not running, before any order is created — instead of accepting an order that would sit queued indefinitely.
  • A user data stream that falls too far behind is CLOSED (1011, "queue overflow; resnapshot") rather than silently dropping events. Reconnect and resnapshot via REST, as documented.
  • A user data stream stops delivering for a login that leaves the key's scope, within about 30 seconds, instead of at the listenKey's 24-hour expiry. The socket closes when nothing remains in scope.
  • A malformed control frame no longer closes the connection. A wrong-typed method or params item answers the documented error frame and the socket stays open.

v1.0.0 (draft) — initial contract

Status: draft. The service is being built to this document. Endpoints, field names and error codes are the contract; base URLs are placeholders pending DNS. Nothing is live yet.

REST

  • General: GET /v1/ping, GET /v1/time, GET /v1/exchangeInfo.
  • Market data: GET /v1/ticker/price, GET /v1/ticker/bookTicker, GET /v1/ticker/24hr, GET /v1/klines, GET /v1/ticks.
  • Trade: POST /v1/order, PUT /v1/order, DELETE /v1/order, GET /v1/order, GET /v1/openOrders, GET /v1/allOrders, GET /v1/positions, PUT /v1/position, DELETE /v1/position, DELETE /v1/allOpenPositions, POST /v1/batchOrders.
  • Account: GET /v1/account, GET /v1/accounts, GET /v1/userTrades.
  • User data stream: POST /v1/listenKey, PUT /v1/listenKey, DELETE /v1/listenKey.

WebSocket

  • Market streams: <SYMBOL>@tick, <SYMBOL>@bookTicker, <SYMBOL>@kline_<interval>, <SYMBOL>@ticker, <SYMBOL>@depth (optional, enabled on request).
  • User data stream events: DEAL, ORDER_UPDATE, POSITION_UPDATE, ACCOUNT_UPDATE, listenKeyExpired.

Model

  • Binance-style conventions: X-YBX-APIKEY, HMAC-SHA256 over query string + body, recvWindow, weight and order-count headers, 429 → 418 escalation, {"code","msg"} errors, SUBSCRIBE/UNSUBSCRIBE/LIST_SUBSCRIPTIONS, combined {"stream","data"} frames.
  • MT5 semantics: lots, bid/ask, tickets, SL/TP on the position, swaps. No mark price, no funding, no liquidation engine.
  • Hedging only — many position tickets per symbol, no positionSide.
  • A mandatory login on every account-scoped call — one key trades many MT5 accounts. The one deliberate departure from Binance's key-is-an-account model.
  • newClientOrderId is the idempotency key. A retry with the same id never creates a second order.
  • Order lifecycle: newOrderRespType ACK (default) / RESULT, with ACCEPTED, FILLED, REJECTED and IN_DOUBT statuses for market executions.
  • mt5RetCode passthrough on execution errors.
  • Sandbox is demo-group MT5 logins on the same endpoints — there is no separate testnet host.

Known limits in v1

  • No account provisioning. Accounts are created by the broker.
  • No deep tick history — GET /v1/ticks serves an in-memory ring buffer.
  • No 1w / 1M kline intervals; 1d buckets are UTC-aligned, not broker-session aligned.
  • ACCOUNT_UPDATE carries balance and credit only; equity and margin need GET /v1/account.
  • No commission on an open position. Commission is booked on deals — read it from GET /v1/userTrades or the DEAL event's n field. GET /v1/positions carries profit and swap only.
  • The trading surface can be switched off independently of market data and account reads; TRADE endpoints and the user data stream then return -1017.
  • bookTicker sizes are always "0" — MT5 quotes carry no top-of-book size.
  • @depth is off unless the broker enables it for the symbol. Subscribing always succeeds and then delivers nothing — silence means "not enabled", not "not subscribed".
  • GET /v1/exchangeInfo is not filtered to your key's scope. Sending your key changes rateLimits[] only; the symbol list is every instrument on the trade server, because the Manager API has no bulk per-group symbol read.
  • GET /v1/userTrades returns closing deals only (entry is OUT, INOUT or OUT_BY, never IN), and its side is the direction as stored by the broker's history writer, which is not always the deal's own direction. Use the DEAL event's S for an unambiguous deal direction.
  • On ORDER_UPDATE, tif is inferred (GTD when an expiration is set, GTC otherwise — DAY and GTD_DAY are not distinguishable), sp is always "0", and ot may be BUY/SELL for a market order passing through the book.
  • ticker/24hr.priceChangePercent (and @ticker's P) has two provenances — the trade server's own statistic on the read path, derived from open/last on the streamed path. They agree to rounding.
  • A market-data or account read that cannot reach the trade server returns -5015 with no mt5RetCode.