# YBX Trading API — full documentation > REST and WebSocket API for programmatic trading on Yellow Box Markets MT5 accounts. Binance-style conventions, native MT5 semantics. Every page of the documentation in sidebar order. Each page starts after a separator that gives its title and the URL of its Markdown version. Index: /llms.txt ================================================================================ Page: Introduction URL: /index.md ================================================================================ # Introduction REST and WebSocket API for programmatic trading on Yellow Box Markets MT5 accounts. The conventions (authentication, signing, headers, error envelope, rate-limit model, WebSocket subscribe protocol) are deliberately modelled on the Binance USDⓈ-M Futures API, so an existing Binance client is mostly a matter of changing the base URL, the header name and the field names. The **semantics are MT5**, not Binance: | MT5 semantics used here | What this replaces | | - | - | | Volume is in **lots** | not contracts or base-asset quantity | | **Bid / ask** quotes, spread priced by the broker | no mark price, no index price | | **Hedging only** — many position tickets per symbol, each with its own ticket id | no one-way/netting mode, no `positionSide` | | **SL/TP live on the position**, modified in place | no separate stop orders to manage | | **Swaps** charged by the trade server | no funding rate, no funding interval | | Margin and stop-out are the trade server's | no liquidation engine, no ADL, no insurance fund | | Symbols are MT5 symbol names, **case-sensitive, exact** | no upper/lower-casing anywhere | There is **no wire compatibility with Binance**. Field names, enum values and error codes are ours. ## Pages | Page | Contents | | - | - | | [General Info](/general-info.md) | Base URLs, authentication and signing (worked example), the `login` parameter model, rate limits, HTTP status codes, error shape, data conventions, versioning policy | | [Market Data Overview](/market-data.md) | `ping`, `time`, `exchangeInfo`, tickers, klines, recent ticks | | [Order Lifecycle](/trade.md) | **Order lifecycle** (read this first), place / modify / cancel orders, positions, batch orders | | [Account Overview](/account.md) | Account state, the logins a key may trade, deal history | | [User Data Streams Overview](/user-data-streams.md#listenkey-management) | `listenKey` create / keepalive / close | | [WebSocket Market Streams Overview](/websocket-market-streams.md) | Connect, subscribe protocol, limits, every market stream | | [User Data Streams Overview](/user-data-streams.md) | Connect with a `listenKey`, every account event, ordering and reconnect rules | | [Common Definition](/common-definition.md) | Every enum, with its MT5 integer, and the `mt5RetCode` table | | [Error Codes](/error-codes.md) | Every error code by family, with exact `msg` text | | [Known Limitations](/known-limitations.md) | **Pre-release:** where the current trade server falls short of the contract, and what to do meanwhile | | [Change Log](/changelog.md) | Version history | | [openapi.yaml](/openapi.yaml) | Machine-readable OpenAPI 3.1 contract for the REST surface | ## Quick start ### 1. Get credentials The broker issues you an **API key** and an **API secret**, plus the list of MT5 **logins** the key may trade. The secret is shown once and is never recoverable — store it in your secret manager. A key is bound to a set of logins. **Every account-scoped call takes a mandatory `login` parameter**, because one key normally trades many accounts. See [general-info.md § The `login` parameter](/general-info.md#the-login-parameter). ### 2. Sign a request SIGNED endpoints take `timestamp` (Unix ms) and `signature` (hex HMAC-SHA256 of the request parameters with your secret). Full rules and a reproducible worked example: [general-info.md § SIGNED endpoint security](/general-info.md#signed-endpoint-security-trade-and-user_data). ### 3. Read the account ```bash curl -H "X-YBX-APIKEY: " \ "https://trade-api.yellowboxmarkets.com/v1/account?login=100123&recvWindow=5000×tamp=1789012345678&signature=13c783f085a1ef46b6a0e1df8d59c70ab50a4f8e669ffa4a2bc49998c0a9255b" ``` ```json { "login": 100123, "group": "real\\BOT\\ECN", "currency": "USD", "leverage": 100, "balance": "10000.0000", "credit": "0.0000", "equity": "10012.3400", "margin": "233.1200", "freeMargin": "9779.2200", "marginLevel": "4294.60", "profit": "12.3400", "tradeAllowed": true, "updateTime": 1789012345678 } ``` ### 4. Place a market order Default `newOrderRespType` is `ACK` — the order is durably queued and the call returns immediately. This is the recommended mode when you fan one signal out across many accounts. ```bash curl -X POST -H "X-YBX-APIKEY: " \ -d "login=100123&symbol=XAUUSD&side=BUY&type=MARKET&volume=0.10&newClientOrderId=bot-001&recvWindow=5000×tamp=1789012345678&signature=b7f6052aa37e7d6de935cce1c099f8103a2b0a0711b77fdf48becfc8b7d2c0e7" \ "https://trade-api.yellowboxmarkets.com/v1/order" ``` ```json { "login": 100123, "clientOrderId": "bot-001", "symbol": "XAUUSD", "side": "BUY", "type": "MARKET", "volume": "0.10", "status": "ACCEPTED", "transactTime": 1789012345690 } ``` `newClientOrderId` is the **idempotency key**. Always set it. Resending the same id never creates a second order — see [rest-api/trade.md § Order lifecycle](/trade.md#order-lifecycle). ### 5. Subscribe to prices ``` wss://trade-stream.yellowboxmarkets.com/ws ``` ```json {"method":"SUBSCRIBE","params":["XAUUSD@tick"],"id":1} ``` ```json {"result":null,"id":1} ``` ```json {"e":"tick","E":1789012345678,"s":"XAUUSD","b":"2331.15","a":"2331.42","l":"2331.15","v":8,"bd":1,"ad":-1,"T":1789012345678} ``` ### 6. Subscribe to your own executions ```bash curl -X POST -H "X-YBX-APIKEY: " "https://trade-api.yellowboxmarkets.com/v1/listenKey" ``` ```json {"listenKey":"pqia91ma19a5s61cv6a81va65sdf19v8a65a1a5s61cv6a81va65sdf19v8a65a1"} ``` Then connect to `wss://trade-stream.yellowboxmarkets.com/ws/`. One connection carries events for **every** login the key may trade; each event carries `L` (the login). Keep the key alive with `PUT /v1/listenKey` every 30 minutes. See [User Data Streams Overview](/user-data-streams.md). ## Sandbox There is **no separate testnet host**. Sandbox testing runs against the same base URLs with a key scoped to **demo-group MT5 logins**. Ask the broker for a demo-scoped key; `GET /v1/accounts` reports `"demo": true` for those logins. Demo accounts behave identically at the API layer — same signing, same rate limits, same streams. Do not assume a key is demo-only because you asked for one. Check `GET /v1/accounts` before your first order. A demo-scoped key lists exactly its demo logins: the broker issues it with an explicit login list and nothing else, so accounts added to your production key never appear on it. ## Support Integration contact, key issuance and incident escalation: _to be provided by the broker (placeholder — no public support channel is published for this API yet)._ Report a suspected contract bug against a specific endpoint, request id and UTC timestamp. Every response carries the key's rate-limit headers, which help correlate. ================================================================================ Page: Use with AI agents URL: /ai-agents.md ================================================================================ # Use with AI agents This documentation is also published for AI coding agents: Markdown pages, an `llms.txt` index, and an **Agent Skill**. The skill teaches an agent the parts of this API that are easy to get wrong — signing, the mandatory `login`, idempotent retries, the user data stream. ## Markdown pages Every page is available as Markdown at the same address with `.md` appended: | Page | Markdown | | - | - | | `https:///trade/new-order/` | `https:///trade/new-order.md` | | `https:///` | `https:///index.md` | The Markdown is generated from the same source as the page you are reading, keeps the same sections (**API Description**, **HTTP Request**, **Request Parameters**, **Response Example**…), and links to the Markdown version of every other page. Paste a page's URL plus `.md` into a chat, or let an agent fetch it. ## Copy page The **Copy page** button next to every page title copies that page's Markdown to the clipboard, ready to paste into a conversation. Its menu also has **View as Markdown**, and links to the two files below. ## llms.txt and llms-full.txt | File | Contents | | - | - | | [`/llms.txt`](/llms.txt) | The index, in the [llmstxt.org](https://llmstxt.org) format: every page, grouped like the sidebar, with a link to its Markdown and a one-line description. Give an agent this URL and it can find the page it needs. | | [`/llms-full.txt`](/llms-full.txt) | Every page's Markdown in one file, in sidebar order. Use it when an agent should hold the whole API at once. | ## Agent Skill The `ybx-trading-api` skill packages the essentials of this API as an [Agent Skill](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview): a short `SKILL.md` that the agent loads when a task involves this API, plus reference files generated from this documentation (an endpoint index, General Info, the order lifecycle, the user data stream, error codes, enums, known limitations and the full docs), which it reads only when needed. - Download: [`/ybx-trading-api-skill.zip`](/ybx-trading-api-skill.zip) - The skill's instructions on their own: [`/agent-skill/SKILL.md`](/agent-skill/SKILL.md) The zip contains one folder, `ybx-trading-api/`. The reference files are rebuilt with every release of this site, so they always match these pages. ### Install for Claude Code For yourself, in every project: ```bash mkdir -p ~/.claude/skills curl -fsSL -o /tmp/ybx-trading-api-skill.zip https:///ybx-trading-api-skill.zip unzip -o /tmp/ybx-trading-api-skill.zip -d ~/.claude/skills/ ``` For one repository — commit it and everyone working in the repository gets it: ```bash mkdir -p .claude/skills curl -fsSL -o /tmp/ybx-trading-api-skill.zip https:///ybx-trading-api-skill.zip unzip -o /tmp/ybx-trading-api-skill.zip -d .claude/skills/ ``` Either way the result is `skills/ybx-trading-api/SKILL.md`. Claude Code discovers the skill on its next start and uses it whenever a task involves this API. To update, download and unzip again. ### Other agents Any agent that supports the Agent Skills format can use the same folder — for example, upload the zip as a custom skill in Claude apps. Agents without skill support can be given `/llms.txt` or the contents of `SKILL.md` as instructions. > The skill contains no credentials and calls nothing by itself. Keep your API key and secret in > environment variables or a secret manager, never in the skill folder or a prompt. ================================================================================ Page: Change Log URL: /changelog.md ================================================================================ # Change Log Newest first. Backward-compatibility rules: [general-info.md § Versioning](/general-info.md#versioning-and-backward-compatibility). 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)](/known-limitations.md)** — 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_`. - **`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](/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: `@tick`, `@bookTicker`, `@kline_`, `@ticker`, `@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`. ================================================================================ Page: Known Limitations URL: /known-limitations.md ================================================================================ # Known Limitations **This API is pre-launch.** The other pages describe the contract — the behaviour you should build against. This page lists where the service currently falls short of that contract, as observed in the first session against the new MT5 trade server on **2026-10-08**. Each entry says what you will see, which endpoints it affects, and what to do until it is fixed. Entries are removed (and the removal noted in the [changelog](/changelog.md)) as fixes land; nothing here changes a field, an enum or an error code. ## The trade server sends no dealer answer **Affects:** [`POST /v1/order`](/trade/new-order.md) (pending types), [`PUT /v1/order`](/trade/modify-pending-order.md), [`DELETE /v1/order`](/trade/cancel-pending-order.md), [`PUT /v1/position`](/trade/modify-position-sltp.md), and every rejection. The current trade server does not return an answer to the requests this API sends. The service therefore learns an outcome only from what the trade server _records_: - **Market orders and closes still settle.** A market `POST /v1/order`, a [`DELETE /v1/position`](/trade/close-position.md) and each [`DELETE /v1/allOpenPositions`](/trade/close-all-open-positions.md) leg produce a deal, the deal is picked up from the deal stream, and the order settles to `FILLED` — inside the 5-second `RESULT` wait when the deal arrives in time. - **Operations that create no deal stay at their queued status.** A pending placement or a `PUT /v1/order` keeps `NEW`; a `DELETE /v1/order` or a `PUT /v1/position` keeps `ACCEPTED`. The response arrives after the 5-second wait with that status and no `mt5RetCode`. - **A rejection is not reported as `REJECTED`.** A request the trade server refuses (insufficient margin, invalid stops, market closed…) stays `ACCEPTED` / `NEW` instead of returning the mapped error with its `mt5RetCode`. **What to do:** confirm every non-market operation from the account state, not from the response — [`ORDER_UPDATE`](/user-data-streams/order_update.md) and [`POSITION_UPDATE`](/user-data-streams/position_update.md) on the user data stream, or [`GET /v1/openOrders`](/trade/current-pending-orders.md) and [`GET /v1/positions`](/trade/open-positions.md). Do **not** resend with a new `newClientOrderId` because a response says `ACCEPTED`. ## `NEW` on a pending placement means "queued", not "resting" **Affects:** [`POST /v1/order`](/trade/new-order.md) and [`POST /v1/batchOrders`](/trade/place-multiple-orders.md) items with a pending `type`. A pending order is reported `NEW` from the moment it is durably queued — on an `ACK` response and on a `RESULT` response that timed out alike (see [Status values](/trade.md#status-values)). With no dealer answer from the current server, that is the only status a placement ever reports. Until the order shows up on the book, `NEW` does not prove it is there: confirm it with `GET /v1/openOrders` or an `ORDER_UPDATE` event carrying its `orderId`. ## Modify / cancel / query by `origClientOrderId` does not find a pending order **Affects:** [`PUT /v1/order`](/trade/modify-pending-order.md), [`DELETE /v1/order`](/trade/cancel-pending-order.md) and [`GET /v1/order`](/trade/query-order.md) when called with `origClientOrderId` for a pending order. Because the placement never receives an answer (above), the service never learns the order ticket the trade server assigned to it. `PUT` and `DELETE` by `origClientOrderId` therefore answer `-2013`, and `GET /v1/order?origClientOrderId=` reports the stored queued status rather than the live state of the order on the book. **What to do:** address pending orders by **`orderId`**. Take it from the `o` field of the `ORDER_UPDATE` event or from `GET /v1/openOrders`. `PUT /v1/order` by `orderId` works. ## Cancelling a pending order currently fails on the trade server **Affects:** [`DELETE /v1/order`](/trade/cancel-pending-order.md). On the current server a cancel is not applied: the order stays on the book and the response never reaches `CANCELED`. Check `GET /v1/openOrders` before assuming an order is gone, and contact the broker if an order must be removed urgently. ## `GET /v1/userTrades` is empty on staging **Affects:** [`GET /v1/userTrades`](/account/account-trade-list.md) in the sandbox / staging environment. The broker-side deal-history sync that this endpoint reads from is not running on staging, so the endpoint returns `[]` there even after trades. Use the [`DEAL`](/user-data-streams/deal.md) events on the user data stream to record executions while testing. ## Server time zone Not a defect, but observed on the first live session: the current trade server reports `serverTimeZoneOffsetMinutes: 180` (UTC+3) on [`GET /v1/exchangeInfo`](/market-data/exchange-information.md). Symbol `sessions` are in that zone. Always read the offset from `exchangeInfo` rather than hard-coding it — it can change (for example with daylight saving on the server). ================================================================================ Page: General Info URL: /general-info.md ================================================================================ # General Info ## Environments and base URLs | Purpose | URL | | - | - | | REST | `https://trade-api.yellowboxmarkets.com` — all paths are prefixed `/v1`, so the effective base is `https://trade-api.yellowboxmarkets.com/v1` | | WebSocket market streams | `wss://trade-stream.yellowboxmarkets.com/ws` (raw) and `wss://trade-stream.yellowboxmarkets.com/stream?streams=` (combined) | | WebSocket user data stream | `wss://trade-stream.yellowboxmarkets.com/ws/` | > **These two hostnames are placeholders and are subject to confirmation.** DNS has not been > assigned yet. Keep the base URL in configuration, not in code. Nothing else in this > documentation changes when the hostnames are finalised. This is the only place the caveat is > stated; every other page uses these URLs as if final. **Sandbox is not a separate host.** A key can be scoped to demo-group MT5 logins and is then functionally a testnet key on the same endpoints. `GET /v1/accounts` reports `"demo": true` per login. ## Request format - `GET` and `DELETE` take parameters in the **query string**. - `POST` and `PUT` take parameters in the query string, in an `application/x-www-form-urlencoded` **body**, or both. - If a parameter appears in both, **the query string value wins**. - Parameters may be sent in any order. - Responses are always JSON (`Content-Type: application/json`), including error responses. - All requests must be HTTPS. Plain HTTP is refused. ## Security types Every endpoint declares a security type. It tells you what must accompany the request. | Security type | `X-YBX-APIKEY` header | `timestamp` + `signature` | | - | - | - | | `NONE` | no | no | | `MARKET_DATA` | yes | no | | `USER_STREAM` | yes | no | | `USER_DATA` | yes | **yes** | | `TRADE` | yes | **yes** | `TRADE` and `USER_DATA` are cryptographically identical. They differ in the **permission** the key must hold: a read-only key can call `USER_DATA` endpoints but is refused (`-2015`) on `TRADE` endpoints. Ask for a read-only key for any component that only needs to observe. `MARKET_DATA` needs the key so the request can be attributed to a rate-limit bucket. It is not account-scoped and takes no `login`. ## The `login` parameter **This is the one deliberate departure from Binance.** On Binance an API key _is_ an account. Here one key normally trades **many** MT5 accounts, so the account cannot be inferred from the key. > **Every account-scoped endpoint takes a mandatory `login` parameter** — the int64 MT5 login of > the trading account the call applies to. Omitting it is `-1102`. Sending a login the key is not > scoped to, that does not exist, whose owner is inactive, or whose account is not active, is > `-2022` — the same error for all of them, so a key cannot probe for the existence of logins it > does not own. A login that IS in scope but has trading **disabled** is a different case, and is deliberately not hidden: reads succeed and report `tradeAllowed: false`, while anything that would trade is refused with `-2024`. That is what makes `tradeAllowed` on [`GET /v1/account`](/account/account-information.md) a value you can act on rather than an error you have to provoke. - `GET /v1/accounts` lists exactly the logins the key may trade. - `POST /v1/batchOrders` carries `login` **per item**, so one call can fan a signal out across many accounts. - The user-data stream is **per key, not per login**: one connection carries events for every login in scope and each event carries `L` (the login). - Market-data endpoints and market streams take no `login`. ## SIGNED endpoint security (`TRADE` and `USER_DATA`) SIGNED endpoints require two extra parameters: | Name | Type | Mandatory | Description | | - | - | - | - | | `timestamp` | LONG | YES | Request creation time, Unix **milliseconds**. Missing is `-1102`; not an integer is `-1130`. | | `signature` | STRING | YES | Lowercase hex HMAC-SHA256 of `totalParams`, keyed with your API secret. | | `recvWindow` | LONG | NO | How long the request stays valid, in milliseconds. Default `5000`, maximum `60000`; outside `1`–`60000` is `-1130`. | ### `totalParams` ``` totalParams = + ``` Concatenated **verbatim, in that order, exactly as transmitted** — no separator is inserted, and the parameters are the percent-encoded bytes you actually put on the wire. Sign last, and never re-order or re-encode a parameter after signing. `signature` itself is never part of `totalParams`. Send it last, either in the query string or in the body. Because concatenation is literal, splitting parameters between the query string and the body produces a different `totalParams` than sending the same parameters in one place. **Send all parameters in one place.** The mixed case is supported for Binance-client compatibility, not recommended. ### Timing security The server computes: ``` timestamp < serverTime + 1000 && serverTime - timestamp <= recvWindow ``` Both conditions must hold, otherwise `-1021`. The `+1000` ms tolerance absorbs a small clock lead; `recvWindow` bounds how stale a request may be. Keep `recvWindow` small. A large window widens the replay window of a request captured in flight. `5000` is right for almost everyone. Sync your clock against `GET /v1/time`, not against a public NTP pool, and resync if you see `-1021` clustering. ### Worked example Use these exact values to validate your signing code. ``` apiKey : Tq7sVn2LpZ4wKdRj9xYc3BmHfA6eQu1oGi0tNsXrWvJbEyPzMkCdHlFgAoUvQ secret : x9Tq2mWvJb4rZ8kLpN6yHc3sVd1fGaQeR7uI0oP5tYbXnMwKjSzCvBhDlFgA ``` **Example 1 — all parameters in the query string** (`GET /v1/account`) ``` queryString : login=100123&recvWindow=5000×tamp=1789012345678 signature : 13c783f085a1ef46b6a0e1df8d59c70ab50a4f8e669ffa4a2bc49998c0a9255b ``` Reproduce it: ```bash echo -n "login=100123&recvWindow=5000×tamp=1789012345678" \ | openssl dgst -sha256 -hmac "x9Tq2mWvJb4rZ8kLpN6yHc3sVd1fGaQeR7uI0oP5tYbXnMwKjSzCvBhDlFgA" ``` ``` SHA2-256(stdin)= 13c783f085a1ef46b6a0e1df8d59c70ab50a4f8e669ffa4a2bc49998c0a9255b ``` ```bash curl -H "X-YBX-APIKEY: Tq7sVn2LpZ4wKdRj9xYc3BmHfA6eQu1oGi0tNsXrWvJbEyPzMkCdHlFgAoUvQ" \ "https://trade-api.yellowboxmarkets.com/v1/account?login=100123&recvWindow=5000×tamp=1789012345678&signature=13c783f085a1ef46b6a0e1df8d59c70ab50a4f8e669ffa4a2bc49998c0a9255b" ``` **Example 2 — all parameters in the body** (`POST /v1/order`) ``` requestBody : login=100123&symbol=XAUUSD&side=BUY&type=MARKET&volume=0.10&newClientOrderId=bot-001&recvWindow=5000×tamp=1789012345678 signature : b7f6052aa37e7d6de935cce1c099f8103a2b0a0711b77fdf48becfc8b7d2c0e7 ``` ```bash echo -n "login=100123&symbol=XAUUSD&side=BUY&type=MARKET&volume=0.10&newClientOrderId=bot-001&recvWindow=5000×tamp=1789012345678" \ | openssl dgst -sha256 -hmac "x9Tq2mWvJb4rZ8kLpN6yHc3sVd1fGaQeR7uI0oP5tYbXnMwKjSzCvBhDlFgA" ``` ``` SHA2-256(stdin)= b7f6052aa37e7d6de935cce1c099f8103a2b0a0711b77fdf48becfc8b7d2c0e7 ``` ```bash curl -X POST \ -H "X-YBX-APIKEY: Tq7sVn2LpZ4wKdRj9xYc3BmHfA6eQu1oGi0tNsXrWvJbEyPzMkCdHlFgAoUvQ" \ -d "login=100123&symbol=XAUUSD&side=BUY&type=MARKET&volume=0.10&newClientOrderId=bot-001&recvWindow=5000×tamp=1789012345678&signature=b7f6052aa37e7d6de935cce1c099f8103a2b0a0711b77fdf48becfc8b7d2c0e7" \ "https://trade-api.yellowboxmarkets.com/v1/order" ``` **Example 3 — mixed query string and body** (the same parameters as Example 2, split) ``` queryString : login=100123&symbol=XAUUSD requestBody : side=BUY&type=MARKET&volume=0.10&newClientOrderId=bot-001&recvWindow=5000×tamp=1789012345678 totalParams : login=100123&symbol=XAUUSDside=BUY&type=MARKET&volume=0.10&newClientOrderId=bot-001&recvWindow=5000×tamp=1789012345678 signature : 2cdb79a28695a12db0f60b35af89e340588d1428b97e291cb013dbff437ac392 ``` Note the missing `&` between `XAUUSD` and `side` — that is the literal concatenation, and it is why the signature differs from Example 2 even though the parameters are identical. Send everything in one place and this case never arises. ### Python ```python import hashlib import hmac import time import urllib.parse import requests BASE = "https://trade-api.yellowboxmarkets.com" API_KEY = "Tq7sVn2LpZ4wKdRj9xYc3BmHfA6eQu1oGi0tNsXrWvJbEyPzMkCdHlFgAoUvQ" API_SECRET = b"x9Tq2mWvJb4rZ8kLpN6yHc3sVd1fGaQeR7uI0oP5tYbXnMwKjSzCvBhDlFgA" def sign(params: dict) -> str: # Encode once, sign exactly those bytes, send exactly those bytes. query = urllib.parse.urlencode(params) signature = hmac.new(API_SECRET, query.encode(), hashlib.sha256).hexdigest() return f"{query}&signature={signature}" def signed_post(path: str, params: dict): params = {**params, "recvWindow": 5000, "timestamp": int(time.time() * 1000)} return requests.post( BASE + path, headers={ "X-YBX-APIKEY": API_KEY, "Content-Type": "application/x-www-form-urlencoded", }, data=sign(params), timeout=10, ).json() print(signed_post("/v1/order", { "login": 100123, "symbol": "XAUUSD", "side": "BUY", "type": "MARKET", "volume": "0.10", "newClientOrderId": "bot-001", })) ``` The `sign()` helper signs the already-encoded string and sends that exact string as the body. Do not pass a `dict` to `requests` after signing a separately-built string — the library may re-encode in a different order and the signature will not match. ## Rate limits Limits are enforced **per API key** across all IPs. | Bucket | Limit | Counts | | - | - | - | | `REQUEST_WEIGHT` | **1200** per minute | Every request, by its endpoint weight. | | `ORDERS` | **100** per 10 seconds | Every order-affecting request. | | `ORDERS` | **1200** per minute | Same. | **These numbers are tunable configuration, not contract.** They may be raised or lowered per key. `GET /v1/exchangeInfo` returns the live values in `rateLimits[]` when you send your `X-YBX-APIKEY` header on it (without a key it returns the defaults) — read them at start-up rather than hard-coding these figures. An order-affecting request is one that places, modifies, cancels or closes something: `POST /v1/order`, `PUT /v1/order`, `DELETE /v1/order`, `PUT /v1/position`, `DELETE /v1/position`, `DELETE /v1/allOpenPositions`, `POST /v1/batchOrders`. `DELETE /v1/allOpenPositions` counts as **one** regardless of how many positions it closes; `POST /v1/batchOrders` counts as **one per item**. ### Headers Every response to a request made with an API key carries that key's current usage (a `NONE` endpoint called without a key is not metered and carries none): | Header | Meaning | | - | - | | `X-YBX-USED-WEIGHT-1M` | Request weight used in the current minute. | | `X-YBX-ORDER-COUNT-10S` | Order-affecting requests in the current 10-second window. | | `X-YBX-ORDER-COUNT-1M` | Order-affecting requests in the current minute. | Track these. Back off before you hit the limit rather than after. ### Breaching a limit | Status | Meaning | | - | - | | `429` | A limit was exceeded. The response carries `Retry-After` (seconds). Stop sending until it elapses. | | `418` | You kept sending after a `429`. The key is temporarily banned. | Bans escalate with repeated offences — from minutes to hours to a day. `Retry-After` always carries the remaining ban. A `418` is not a transport error: retrying through it lengthens the ban. **Use the WebSocket streams instead of polling.** Repeatedly polling `GET /v1/positions` or `GET /v1/ticker/price` is the usual way integrators reach these limits; the tick, kline and user-data streams carry the same information without consuming request weight. ## HTTP status codes | Status | Meaning | | - | - | | `2XX` | Success. | | `4XX` | Malformed request. The problem is on your side; do not blind-retry. | | `429` | Rate limit breached. Back off. | | `418` | Key temporarily banned for ignoring `429`. | | `5XX` | Internal error. **This is a problem on our side. It is not an indication that the operation failed.** | ### The two meanings of `503` `503` carries a body. Read `msg` before deciding what to do. ```json {"code":-1000,"msg":"Unknown error, please check your request or try again later. The execution status is UNKNOWN and could have been a success."} ``` The request may have reached the trade server and may have executed. **Do not retry blindly.** (This message is reserved: the current implementation reports an unknown outcome as `-5008` or `-5009` instead — see [error-codes.md § Reading `-5008` and `-5009`](/error-codes.md#reading--5008-and--5009). Handle all three the same way.) Confirm first: watch for a `DEAL` event on the user data stream, or call `GET /v1/order?origClientOrderId=` with the same client order id. If you always set `newClientOrderId`, a retry is safe anyway — it is idempotent and returns the stored outcome. ```json {"code":-1000,"msg":"Service is currently unavailable, please try again later."} ``` The service refused the request before doing anything. Retry with exponential backoff. ## Error response format Every error, at any status, has the same shape: ```json {"code":-1121,"msg":"Invalid symbol."} ``` | Field | Type | Description | | - | - | - | | `code` | INT | Negative error code. Stable — safe to branch on. | | `msg` | STRING | Human-readable English text. Informational; do not parse or branch on it. | | `mt5RetCode` | INT | **Execution errors only.** The raw MT5 trade-request return code, passed through unchanged. | An execution error looks like this: ```json {"code":-2018,"msg":"Insufficient margin on the trading account.","mt5RetCode":10019} ``` `mt5RetCode` is present only when the trade server actually answered. Its values are listed in [enums.md § MT5 request return codes](/common-definition.md#mt5-request-return-codes-mt5retcode). The full error list is in [Error Codes](/error-codes.md). ## Data conventions ### Decimals are strings Every decimal value — prices, volumes, money — is serialised as a JSON **string**, so no precision is lost by a JSON parser that stores numbers as IEEE doubles. ```json {"volume":"0.10","priceOpen":"2331.20","profit":"-4.5000"} ``` Parse them into a decimal type. Never into a float. Integers (`login`, ticket ids, timestamps, counts, digit counts) are JSON numbers. ### Volumes are lots `volume` is always in **lots**, matching the MT5 platform. `0.10` means one tenth of a standard lot. Per-symbol `volumeMin`, `volumeMax` and `volumeStep` from `GET /v1/exchangeInfo` are also lots, and a volume that is not an exact multiple of `volumeStep` is rejected (`-4007`). The lot's notional value is `volume * contractSize` in `currencyBase`. ### Prices are in symbol quote units Prices are raw symbol prices at the symbol's `digits` precision — the same numbers the MT5 terminal shows. Nothing is normalised across symbols. ### Symbols are case-sensitive and exact **Use the symbol name exactly as the trade server spells it.** `BTCUSD.cent` and `BTCUSD.CENT` are two different instruments, and a symbol that differs only in case does not exist (`-1121`). This includes **stream names**. Unlike Binance, stream names are **not** lowercased: ``` XAUUSD@tick correct xauusd@tick WRONG — no such stream BTCUSD.cent@tick correct ``` Enumerate the exact names with `GET /v1/exchangeInfo`. Never upper-case, lower-case or otherwise normalise a symbol anywhere in your stack. ### Times are Unix milliseconds Every timestamp on the wire is Unix epoch **milliseconds**, UTC. MT5 is natively second-resolution for trade timestamps (`time`, `timeSetup`, `timeExpiration`, `openTime`), so those fields are exact multiples of 1000 after conversion. Millisecond precision on those fields is a unit convention, not extra resolution. ### Money is in the account's deposit currency `balance`, `credit`, `equity`, `margin`, `freeMargin`, `profit`, `swap` and `commission` are in the account's deposit currency. `GET /v1/account` returns it as `currency`. > **Cent accounts.** Some MT5 groups are denominated in cents — `currency` is `USC`, and > 100 USC = 1 USD. Values on these accounts are **not** converted to USD anywhere in this API; they > are exactly what the trade server holds. A balance of `"100000.0000"` on a `USC` account is > 1,000 US dollars. Always read `currency` before displaying or aggregating money, and never mix > `USD` and `USC` values in a sum. ### Identifiers | Identifier | Type | Notes | | - | - | - | | `login` | INT64 | MT5 login. | | `orderId` | INT64 | MT5 order ticket. | | `positionId` | INT64 | MT5 position ticket. Under hedging, one per position — a symbol can have many. | | `dealId` | INT64 | MT5 deal ticket. The natural key of an execution. | | `clientOrderId` | STRING | Yours. Up to 36 characters, unique per API key. The idempotency key. | Ticket ids can exceed 2^53, so **do not parse them into a JavaScript `Number`.** Use `BigInt` or keep them as strings. ## Versioning and backward compatibility The `/v1` contract is frozen in the following sense: **We may, without notice:** - Add new fields to any JSON object in a response. - Add new elements to the end of an array row (for example a kline row). - Add new values to any enum (`status`, `type`, event `e`, error `code`). - Add new endpoints, new streams and new optional request parameters. - Change rate-limit numbers (read them from `GET /v1/exchangeInfo`). **Your client must therefore:** - **Ignore unknown fields** rather than fail on them. - **Tolerate unknown enum values** — treat an unrecognised `status`, `type` or event as "something happened I do not model", log it, and do not crash or assume a default. - Index array rows **from the front** and tolerate extra trailing elements. - Never rely on field ordering in a JSON object. **We will not, within `/v1`:** - Remove or rename a field. - Change the type or unit of an existing field. - Change the meaning of an existing enum value or error code. - Remove an endpoint or a stream. Anything that would break those guarantees ships as `/v2`, and `/v1` keeps running alongside it for an announced deprecation period. Every change is recorded in [Change Log](/changelog.md). ================================================================================ Page: Common Definition URL: /common-definition.md ================================================================================ # Common Definition Every enumerated value this API uses, and the MT5 integer behind it. The API always speaks **symbolic strings** (`BUY_LIMIT`, `GTC`, `IN`). The MT5 integers are listed so you can reconcile against an MT5 terminal, the MT5 Manager API, or a raw `mst` / `mt5RetCode` field. > New values may be added to any enum without notice. **Treat an unrecognised value as "something I > do not model"** — log it and carry on. Do not crash and do not silently coerce it to a default. > > Where the trade server reports an MT5 integer this API does not map, the symbol-specification > enums (`tradeMode`, `executionMode`, `swapMode`, `swap3Days`) and `ORDER_UPDATE.ot` carry > `UNKNOWN_`, `n` being the raw integer. ## Security type Per endpoint, in each endpoint's spec block. | Value | API key | Signature | | - | - | - | | `NONE` | no | no | | `MARKET_DATA` | yes | no | | `USER_STREAM` | yes | no | | `USER_DATA` | yes | yes | | `TRADE` | yes | yes | ## Order side | Value | MT5 | Notes | | - | - | - | | `BUY` | `OP_BUY` = 0 | | | `SELL` | `OP_SELL` = 1 | | On a `DEAL` event `S` is the side of the **deal**, which is the opposite of the position's side when `en` is `OUT`. ## Order type | Value | MT5 `EnOrderType` | Pending | Requires | | - | - | - | - | | `MARKET` | `OP_BUY` = 0 / `OP_SELL` = 1 | no | `side` | | `BUY_LIMIT` | `OP_BUY_LIMIT` = 2 | yes | `price` below the ask | | `SELL_LIMIT` | `OP_SELL_LIMIT` = 3 | yes | `price` above the bid | | `BUY_STOP` | `OP_BUY_STOP` = 4 | yes | `price` above the ask | | `SELL_STOP` | `OP_SELL_STOP` = 5 | yes | `price` below the bid | | `BUY_STOP_LIMIT` | `OP_BUY_STOP_LIMIT` = 6 | yes | `price` + `stopLimitPrice` | | `SELL_STOP_LIMIT` | `OP_SELL_STOP_LIMIT` = 7 | yes | `price` + `stopLimitPrice` | `OP_CLOSE_BY` = 8 (close one position with an opposite one) exists on MT5 but is **not exposed** in v1 — you cannot send it. Close positions individually with `DELETE /v1/position`. An order of that type placed outside this API can still surface: `ORDER_UPDATE.ot` reports it as `CLOSE_BY`, and market orders passing through the book as `BUY` / `SELL`; an MT5 type this API does not know is reported as `UNKNOWN_`. ## Time in force Sent as `timeInForce`, reported on orders as `tif`. | Value | MT5 `EnOrderTime` | Meaning | | - | - | - | | `GTC` | `ORDER_TIME_GTC` = 0 | Good till cancelled. Default. | | `DAY` | `ORDER_TIME_DAY` = 1 | Cancelled at the end of the trading day. | | `GTD` | `ORDER_TIME_SPECIFIED` = 2 | Good till the `expiration` timestamp. | | `GTD_DAY` | `ORDER_TIME_SPECIFIED_DAY` = 3 | Good until 00:00 of the `expiration` day, or the nearest trading time. | `GTD` and `GTD_DAY` require `expiration`. Only values present in the symbol's `expirationModes` (from `GET /v1/exchangeInfo`) are accepted; anything else is `-4012`. ## Order status Reported as `status` on REST, `X` on `ORDER_UPDATE`. ### Market executions | Value | Terminal | Meaning | | - | - | - | | `ACCEPTED` | no | Durably queued, not yet confirmed by the trade server. | | `PARTIALLY_FILLED` | no | Part of the requested volume executed (MT5 answered `10010 DONE_PARTIAL`, or the fills so far add up to less than `volume`). `executedVolume` is what has actually filled. More `DEAL` events may follow. | | `FILLED` | yes | Executed. `executedVolume` equals the requested volume. | | `REJECTED` | yes | Refused by the trade server. `mt5RetCode` says why. | | `IN_DOUBT` | no | The service cannot determine whether the operation reached the trade server. Reconcile before acting. | `PARTIALLY_FILLED` applies to `POST /v1/order` with `type=MARKET`, to `DELETE /v1/position` and to each leg of `DELETE /v1/allOpenPositions`. `executedVolume` accumulates across the distinct deals that fill the order and `price` is their **volume-weighted average**; a redelivered deal never double-counts. ### Pending orders | Value | MT5 `EnOrderState` | Terminal | | - | - | - | | `NEW` | `ORDER_STATE_STARTED` = 0, `ORDER_STATE_PLACED` = 1, `ORDER_STATE_REQUEST_ADD` = 7, `ORDER_STATE_REQUEST_MODIFY` = 8, `ORDER_STATE_REQUEST_CANCEL` = 9 | no | | `PARTIALLY_FILLED` | `ORDER_STATE_PARTIAL` = 3 | no | | `FILLED` | `ORDER_STATE_FILLED` = 4 | yes | | `CANCELED` | `ORDER_STATE_CANCELED` = 2 | yes | | `EXPIRED` | `ORDER_STATE_EXPIRED` = 6 | yes | | `REJECTED` | `ORDER_STATE_REJECTED` = 5 | yes | MT5 states 7, 8 and 9 are transient gateway states ("a request to add/modify/cancel is being processed"). They map to `NEW` because the order is still live. The raw value is on `mst` if you need the distinction. ## Order state (raw MT5) The `mst` field on `ORDER_UPDATE`, unmapped. | MT5 `EnOrderState` | Value | Meaning | | - | - | - | | `ORDER_STATE_STARTED` | 0 | Checked, awaiting processing. | | `ORDER_STATE_PLACED` | 1 | Accepted and placed. | | `ORDER_STATE_CANCELED` | 2 | Cancelled by the client. | | `ORDER_STATE_PARTIAL` | 3 | Partially filled. | | `ORDER_STATE_FILLED` | 4 | Filled in full. | | `ORDER_STATE_REJECTED` | 5 | Rejected by the broker. | | `ORDER_STATE_EXPIRED` | 6 | Cancelled on expiration. | | `ORDER_STATE_REQUEST_ADD` | 7 | Placement request in flight. | | `ORDER_STATE_REQUEST_MODIFY` | 8 | Modification request in flight. | | `ORDER_STATE_REQUEST_CANCEL` | 9 | Cancellation request in flight. | ## Deal entry The `entry` field on `GET /v1/userTrades`, `en` on the `DEAL` event. | Value | MT5 `EnDealEntry` | Meaning | | - | - | - | | `IN` | `ENTRY_IN` = 0 | Entering the market, or adding volume to a position. | | `OUT` | `ENTRY_OUT` = 1 | Exiting, or partially closing. | | `INOUT` | `ENTRY_INOUT` = 2 | Closed a position and opened an opposite one in the same deal. Netting accounts only — it does not occur under hedging. | | `OUT_BY` | `ENTRY_OUT_BY` = 3 | Closed simultaneously with an opposite position ("close by"). | Realised profit is non-zero only on `OUT`, `INOUT` and `OUT_BY`. ## Deal action (raw MT5) Only `DEAL_BUY` and `DEAL_SELL` deals are exposed by this API. The rest are listed so you recognise them if you reconcile against an MT5 statement: they are the reason `GET /v1/userTrades` does not sum to the account balance. | MT5 `EnDealAction` | Value | Exposed | | - | - | - | | `DEAL_BUY` | 0 | yes, `side: "BUY"` | | `DEAL_SELL` | 1 | yes, `side: "SELL"` | | `DEAL_BALANCE` | 2 | no — deposit / withdrawal | | `DEAL_CREDIT` | 3 | no — credit granted or removed | | `DEAL_CHARGE` | 4 | no | | `DEAL_CORRECTION` | 5 | no | | `DEAL_BONUS` | 6 | no | | `DEAL_COMMISSION` | 7 | no | | `DEAL_COMMISSION_DAILY` | 8 | no | | `DEAL_COMMISSION_MONTHLY` | 9 | no | | `DEAL_AGENT_DAILY` | 10 | no | | `DEAL_AGENT_MONTHLY` | 11 | no | | `DEAL_INTERESTRATE` | 12 | no | | `DEAL_BUY_CANCELED` | 13 | no | | `DEAL_SELL_CANCELED` | 14 | no | | `DEAL_DIVIDEND` | 15 | no | | `DEAL_DIVIDEND_FRANKED` | 16 | no | | `DEAL_TAX` | 17 | no | | `DEAL_AGENT` | 18 | no | | `DEAL_SO_COMPENSATION` | 19 | no | | `DEAL_SO_COMPENSATION_CREDIT` | 20 | no | Balance-affecting actions surface as an [`ACCOUNT_UPDATE`](/user-data-streams/account_update.md) event instead. ## Position direction | Value | MT5 `EnPositionAction` | | - | - | | `BUY` | `POSITION_BUY` = 0 | | `SELL` | `POSITION_SELL` = 1 | Positions are **hedged**: a symbol can hold any number of `BUY` and `SELL` positions at once, each with its own `positionId`. ## Entity change action The `x` field on `ORDER_UPDATE`, `POSITION_UPDATE` and `ACCOUNT_UPDATE`. | Value | Meaning | | - | - | | `NEW` | The entity appeared. | | `UPDATE` | The entity changed. | | `DELETE` | The entity is gone (`ORDER_UPDATE`, `ACCOUNT_UPDATE`). | | `CLOSE` | The position is gone (`POSITION_UPDATE` only). | ## Symbol trade mode `tradeMode` on `GET /v1/exchangeInfo`. | Value | MT5 `EnTradeMode` | Meaning | | - | - | - | | `DISABLED` | `TRADE_DISABLED` = 0 | No trading at all. | | `LONG_ONLY` | `TRADE_LONGONLY` = 1 | Only long positions may be opened. Rejects with `-4013`. | | `SHORT_ONLY` | `TRADE_SHORTONLY` = 2 | Only short positions may be opened. Rejects with `-4014`. | | `CLOSE_ONLY` | `TRADE_CLOSEONLY` = 3 | Existing positions may be closed; nothing new may be opened. Rejects with `-4015`. | | `FULL` | `TRADE_FULL` = 4 | Unrestricted. | ## Symbol execution mode `executionMode` on `GET /v1/exchangeInfo`. | Value | MT5 `EnExecutionMode` | Meaning | | - | - | - | | `REQUEST` | `EXECUTION_REQUEST` = 0 | Request execution — the broker quotes, you accept. Requotes (`-5001`) are expected. | | `INSTANT` | `EXECUTION_INSTANT` = 1 | Instant execution at the quoted price, subject to requote. | | `MARKET` | `EXECUTION_MARKET` = 2 | Market execution — filled at the server's price, no requote, slippage possible. | | `EXCHANGE` | `EXECUTION_EXCHANGE` = 3 | Exchange execution against the book. | Execution mode determines whether a market order can be requoted and which fill modes apply. ## Fill mode `fillModes` on `GET /v1/exchangeInfo`. Not settable per order in v1 — the service picks a policy the symbol allows. | Value | MT5 `EnOrderFilling` | `EnFillingFlags` bit | Meaning | | - | - | - | - | | `FOK` | `ORDER_FILL_FOK` = 0 | 1 | Fill completely or cancel. | | `IOC` | `ORDER_FILL_IOC` = 1 | 2 | Fill what is available, cancel the rest. | | `RETURN` | `ORDER_FILL_RETURN` = 2 | — | Return the remainder to the book. Pending orders. | | `BOC` | `ORDER_FILL_BOC` = 3 | 4 | Passive — cancel if it would fill immediately. | `RETURN` has no flag bit in the symbol configuration, so it **never appears in `fillModes`**; it is listed here only so `ORDER_FILL_RETURN` is recognisable. A fill policy the symbol does not allow is `-4017`. ## Order modes `orderModes` on `GET /v1/exchangeInfo` — which order categories the symbol permits. | Value | MT5 `EnOrderFlags` | Value | | - | - | - | | `MARKET` | `ORDER_FLAGS_MARKET` | 1 | | `LIMIT` | `ORDER_FLAGS_LIMIT` | 2 | | `STOP` | `ORDER_FLAGS_STOP` | 4 | | `STOP_LIMIT` | `ORDER_FLAGS_STOP_LIMIT` | 8 | | `SL` | `ORDER_FLAGS_SL` | 16 | | `TP` | `ORDER_FLAGS_TP` | 32 | | `CLOSE_BY` | `ORDER_FLAGS_CLOSEBY` | 64 | `SL` and `TP` being absent means the symbol does not accept stop-loss / take-profit levels at all — sending `sl` or `tp` is then `-4016`. ## Swap mode `swapMode` on `GET /v1/exchangeInfo`. Determines the unit of `swapLong` / `swapShort`. | Value | MT5 `EnSwapMode` | `swapLong`/`swapShort` unit | | - | - | - | | `DISABLED` | `SWAP_DISABLED` = 0 | no swap charged | | `BY_POINTS` | `SWAP_BY_POINTS` = 1 | symbol points | | `BY_SYMBOL_CURRENCY` | `SWAP_BY_SYMBOL_CURRENCY` = 2 | `currencyBase` | | `BY_MARGIN_CURRENCY` | `SWAP_BY_MARGIN_CURRENCY` = 3 | `currencyMargin` | | `BY_GROUP_CURRENCY` | `SWAP_BY_GROUP_CURRENCY` = 4 | account deposit currency | | `BY_INTEREST_CURRENT` | `SWAP_BY_INTEREST_CURRENT` = 5 | percent of the current price | | `BY_INTEREST_OPEN` | `SWAP_BY_INTEREST_OPEN` = 6 | percent of the open price | | `REOPEN_BY_CLOSE_PRICE` | `SWAP_REOPEN_BY_CLOSE_PRICE` = 7 | points; the position is closed and reopened nightly | | `REOPEN_BY_BID` | `SWAP_REOPEN_BY_BID` = 8 | points; the position is closed and reopened nightly | | `BY_PROFIT_CURRENCY` | `SWAP_BY_PROFIT_CURRENCY` = 9 | `currencyProfit` | Under `REOPEN_BY_CLOSE_PRICE` and `REOPEN_BY_BID` the trade server **closes and reopens** the position every night. The `positionId` changes and you will see a `POSITION_UPDATE` with `x: "CLOSE"` followed by a new one — this is not a trading event. Handle it, or your bookkeeping will report a phantom round trip every night. ## Swap 3-days `swap3Days` on `GET /v1/exchangeInfo` — the weekday triple swap is charged. | Value | MT5 `EnSwapDays` | | - | - | | `SUNDAY` | 0 | | `MONDAY` | 1 | | `TUESDAY` | 2 | | `WEDNESDAY` | 3 | | `THURSDAY` | 4 | | `FRIDAY` | 5 | | `SATURDAY` | 6 | | `DISABLED` | 7 | `DISABLED` (MT5 `SWAP_DAY_DISABLED`) means the symbol charges no triple swap on any day. ## Kline interval | Value | Minutes | | - | - | | `1m` | 1 | | `5m` | 5 | | `15m` | 15 | | `30m` | 30 | | `1h` | 60 | | `2h` | 120 | | `4h` | 240 | | `1d` | 1440 | Buckets are aligned to the Unix epoch in UTC. `1w` and `1M` are not supported. ## Rate limit type `rateLimits[].rateLimitType` on `GET /v1/exchangeInfo`. | Value | Meaning | | - | - | | `REQUEST_WEIGHT` | Weighted request budget. | | `ORDERS` | Order-affecting request budget. | `interval` is `SECOND`, `MINUTE`, `HOUR` or `DAY`; `intervalNum` multiplies it. ## MT5 request return codes (`mt5RetCode`) Passed through unchanged whenever the trade server answered. These are MT5's `MT_RET_REQUEST_*` codes; the `msg` and `code` in the error envelope are our mapping of them, and this column is the raw truth. | `mt5RetCode` | MT5 constant | Meaning | Maps to | | - | - | - | - | | 10004 | `MT_RET_REQUEST_REQUOTE` | Requote in response to the request. | `-5001` | | 10006 | `MT_RET_REQUEST_REJECT` | Request rejected. | `-5007` | | 10007 | `MT_RET_REQUEST_CANCEL` | Request cancelled. | `-5010` | | 10008 | `MT_RET_REQUEST_PLACED` | Order placed. **Success** for a pending order. | `status: "NEW"` | | 10009 | `MT_RET_REQUEST_DONE` | Request fulfilled. **Success.** | `status: "FILLED"` | | 10010 | `MT_RET_REQUEST_DONE_PARTIAL` | Partially fulfilled. **Success.** | `status: "PARTIALLY_FILLED"` | | 10011 | `MT_RET_REQUEST_ERROR` | Common request error. | `-5007` | | 10012 | `MT_RET_REQUEST_TIMEOUT` | Request timed out. Execution status unknown. | `-5008` | | 10013 | `MT_RET_REQUEST_INVALID` | Invalid request. | `-5017` | | 10014 | `MT_RET_REQUEST_INVALID_VOLUME` | Invalid volume. | `-4004` | | 10015 | `MT_RET_REQUEST_INVALID_PRICE` | Invalid price. | `-4002` | | 10016 | `MT_RET_REQUEST_INVALID_STOPS` | Wrong stop levels or price. | `-4009` | | 10017 | `MT_RET_REQUEST_TRADE_DISABLED` | Trade is disabled. | `-4011` | | 10018 | `MT_RET_REQUEST_MARKET_CLOSED` | Market is closed. | `-4010` | | 10019 | `MT_RET_REQUEST_NO_MONEY` | Not enough money. | `-2018` | | 10020 | `MT_RET_REQUEST_PRICE_CHANGED` | Price has changed. | `-5002` | | 10021 | `MT_RET_REQUEST_PRICE_OFF` | No price. | `-5003` | | 10022 | `MT_RET_REQUEST_INVALID_EXP` | Invalid order expiration. | `-4012` | | 10023 | `MT_RET_REQUEST_ORDER_CHANGED` | Order has been changed. | `-5004` | | 10024 | `MT_RET_REQUEST_TOO_MANY` | Too many trade requests in flight. | `-5011` | | 10025 | `MT_RET_REQUEST_NO_CHANGES` | Request contains no changes. | `-5005` | | 10026 | `MT_RET_REQUEST_AT_DISABLED_SERVER` | Autotrading disabled on the server. | `-5012` | | 10027 | `MT_RET_REQUEST_AT_DISABLED_CLIENT` | Autotrading disabled on the client side. | `-5012` | | 10028 | `MT_RET_REQUEST_LOCKED` | Request blocked by the dealer. | `-5013` | | 10029 | `MT_RET_REQUEST_FROZEN` | Order or position too close to market to modify. | `-4021` | | 10030 | `MT_RET_REQUEST_INVALID_FILL` | Fill mode is not supported. | `-4017` | | 10031 | `MT_RET_REQUEST_CONNECTION` | No connection. | `-5015` | | 10032 | `MT_RET_REQUEST_ONLY_REAL` | Allowed only for real accounts. | `-5014` | | 10033 | `MT_RET_REQUEST_LIMIT_ORDERS` | Order count limit reached. | `-4018` | | 10034 | `MT_RET_REQUEST_LIMIT_VOLUME` | Volume limit reached. | `-4020` | | 10035 | `MT_RET_REQUEST_INVALID_ORDER` | Invalid or prohibited order type. | `-5016` | | 10036 | `MT_RET_REQUEST_POSITION_CLOSED` | Position is already closed. | `-5006` | | 10038 | `MT_RET_REQUEST_INVALID_CLOSE_VOLUME` | Close volume exceeds the position volume. | `-4024` | | 10039 | `MT_RET_REQUEST_CLOSE_ORDER_EXIST` | An order to close this position already exists. | `-4025` | | 10040 | `MT_RET_REQUEST_LIMIT_POSITIONS` | Open position limit reached. | `-4019` | | 10041 | `MT_RET_REQUEST_REJECT_CANCEL` | Request rejected, order cancelled by a routing rule. | `-5018` | | 10042 | `MT_RET_REQUEST_LONG_ONLY` | Only long positions are allowed on the symbol. | `-4013` | | 10043 | `MT_RET_REQUEST_SHORT_ONLY` | Only short positions are allowed on the symbol. | `-4014` | | 10044 | `MT_RET_REQUEST_CLOSE_ONLY` | Only position closing is allowed on the symbol. | `-4015` | | 10045 | `MT_RET_REQUEST_PROHIBITED_BY_FIFO` | Closure not allowed by the FIFO rule. | `-4023` | | 10046 | `MT_RET_REQUEST_HEDGE_PROHIBITED` | Hedge positions are prohibited for the group. | `-4022` | Codes 10001-10003 (`INWAY`, `ACCEPTED`, `PROCESS`), 10005 (`PRICES`) and 10037 (`EXECUTION_SKIPPED`, internal) are intermediate or internal and are never surfaced as a terminal `mt5RetCode`. > 10046 can occur even though this API is hedging-only: the account's **group** can forbid holding > opposite positions on the same symbol. It is a group setting, not an API mode. Full error-code list with exact messages: [Error Codes](/error-codes.md). ================================================================================ Page: Error Codes URL: /error-codes.md ================================================================================ # Error Codes Every error this API returns. ```json {"code":-1121,"msg":"Invalid symbol."} ``` | Field | Type | Description | | - | - | - | | `code` | INT | Negative integer. **Stable — branch on this.** | | `msg` | STRING | English text. Informational; wording may change. Never parse it. | | `mt5RetCode` | INT | Present only when the trade server answered. Raw MT5 return code — see [Common Definition](/common-definition.md#mt5-request-return-codes-mt5retcode). | When an order endpoint (`POST /v1/order`, `PUT /v1/order`, `DELETE /v1/order`, `PUT /v1/position`, `DELETE /v1/position`, `POST /v1/batchOrders` items) recorded the operation and it was then **rejected**, the error body carries three additional fields so the failure can be correlated without a second lookup: ```json {"code":-2018,"msg":"Insufficient margin on the trading account.","mt5RetCode":10019,"status":"REJECTED","clientOrderId":"bot-003","login":100123} ``` A request refused before anything was recorded (a validation, scope or rate-limit error) carries only `code` and `msg` — plus, inside a `POST /v1/batchOrders` response, the item's `login` and `clientOrderId` where they could be read. New codes may be added at any time. Treat an unknown code in the `-10xx`/`-11xx` range as "my request is wrong, do not retry", and an unknown code in the `-50xx` range as "execution problem, status may be unknown". ## `-10xx` — General, server and network Something went wrong that is not about the content of your request. | Code | `msg` | HTTP | Notes | | - | - | - | - | | `-1000` | `Unknown error, please check your request or try again later. The execution status is UNKNOWN and could have been a success.` | 503 | **May have executed.** Confirm before retrying. See [the 503 semantics](/general-info.md#the-two-meanings-of-503). | | `-1000` | `Service is currently unavailable, please try again later.` | 503 | Did not execute. Retry with backoff. Same code, different message — read `msg`. Also the stored outcome of an order the service could not queue within its dispatch window (`GET /v1/order` shows `REJECTED` with this code): it never executed, and retrying the **same** `newClientOrderId` sends it as a fresh order. | | `-1001` | `Internal error; unable to process your request. Please try again.` | 500 | | | `-1002` | `You are not authorized to execute this request.` | 401 | Missing or malformed `X-YBX-APIKEY` header. | | `-1003` | `Too many requests; current limit is %s requests per minute. Please use the WebSocket streams for live updates to avoid polling.` | 429 | `Retry-After` carries the wait. A repeat breach escalates to HTTP 418. | | `-1006` | `An unexpected response was received from the trade server. Execution status is UNKNOWN.` | 503 | Confirm before retrying. | | `-1007` | `Timeout waiting for response from the trade server. Execution status is UNKNOWN.` | 504 | Confirm before retrying. | | `-1008` | `Server is currently overloaded with other requests. Please try again in a few minutes.` | 503 | Did not execute. Also refuses a market-stream WebSocket connection over the per-IP or service-wide connection limit, before the upgrade. | | `-1014` | `Unsupported order combination.` | 400 | The parameters are individually valid but cannot be combined. | | `-1015` | `Too many new orders; current limit is %s orders per %s.` | 429 | The `ORDERS` bucket, not `REQUEST_WEIGHT`. | | `-1016` | `This service is no longer available.` | 410 | A **retired** endpoint. Permanent; do not retry. | | `-1017` | `Trading is temporarily disabled on this API. Retry later.` | 503 | The trading surface is switched off. Market data, market streams and account reads stay available. Retryable — retry with backoff, and alert if it persists. | | `-1020` | `This operation is not supported.` | 400 | A plain HTTP request to a WebSocket URL, or an unknown WebSocket control `method`. | | `-1021` | `Timestamp for this request is outside of the recvWindow.` | 400 | Clock drift, or a request that sat in a queue too long. Sync against `GET /v1/time`. | | `-1022` | `Signature for this request is not valid.` | 401 | Almost always a `totalParams` mismatch — re-encoding after signing, or splitting parameters between query and body. | ## `-11xx` — Request and validation Your request is malformed. Fix it; do not retry unchanged. | Code | `msg` | HTTP | | - | - | - | | `-1100` | `Illegal characters found in a parameter.` | 400 | | `-1101` | `Too many parameters sent for this endpoint.` | 400 | | `-1102` | `A mandatory parameter was not sent, was empty/null, or malformed.` | 400 | | `-1103` | `An unknown parameter was sent.` | 400 | | `-1104` | `Not all sent parameters were read.` | 400 | | `-1105` | `A parameter was empty.` | 400 | | `-1106` | `A parameter was sent when not required.` | 400 | | `-1111` | `Precision is over the maximum defined for this symbol.` | 400 | | `-1115` | `Invalid timeInForce.` | 400 | | `-1116` | `Invalid order type.` | 400 | | `-1117` | `Invalid side.` | 400 | | `-1118` | `New client order ID was empty.` | 400 | | `-1119` | `Client order ID is too long or contains illegal characters.` | 400 | | `-1120` | `Invalid interval.` | 400 | | `-1121` | `Invalid symbol.` | 400 | | `-1122` | `Invalid login.` | 400 | | `-1125` | `This listenKey does not exist.` | 400 | | `-1127` | `Lookup interval is too big.` | 400 | | `-1128` | `Combination of optional parameters invalid.` | 400 | | `-1130` | `Invalid data sent for a parameter.` | 400 | | `-1131` | `batchOrders must contain between 1 and 100 items.` | 400 | | `-1132` | `Duplicate client order ID in batch.` | 400 | | `-1133` | `Client order ID was already used by a different operation.` | 400 | `-1102` covers a missing `timestamp`; a `timestamp` or `recvWindow` that is present but not an integer — or a `recvWindow` outside `1`–`60000` — is `-1130`. A present-but-unparseable numeric or decimal parameter, a `limit` outside its range and an unknown `newOrderRespType` are `-1130` too. `-1106` is returned when `timeInForce` is sent on a `MARKET` order, or `stopLimitPrice` on an order that is not a stop-limit. `-1121` is also what a WebSocket `SUBSCRIBE` returns for a stream name whose symbol does not exist — including one that differs only in **case**. `-1122` means the `login` value is not a valid int64. A login that exists but is not yours is `-2022`. `-1133` is returned on **every** endpoint that takes `newClientOrderId` — including `POST /v1/order` and each `POST /v1/batchOrders` item (as the error object in that item's slot) — when your key already used that id for a different kind of operation **or for a different `login`**. Nothing is executed and nothing is replayed. ## `-20xx` — Processing and authorization The request is well-formed but cannot be carried out. | Code | `msg` | HTTP | Notes | | - | - | - | - | | `-2010` | `New order was rejected.` | 400 | Generic placement rejection where no more specific code applies. | | `-2011` | `Cancel was rejected.` | 400 | | | `-2013` | `Order does not exist.` | 400 | Also returned for an order belonging to a different login — never a permission error. | | `-2014` | `API-key format invalid.` | 401 | | | `-2015` | `Invalid API-key, IP, or permissions for action.` | 401 | Unknown key, revoked key, source IP not allowlisted, or a read-only key on a `TRADE` endpoint. Deliberately not distinguished. | | `-2018` | `Insufficient margin on the trading account.` | 400 | MT5 `10019`. | | `-2021` | `Order would immediately trigger.` | 400 | A pending price on the wrong side of the market. | | `-2022` | `Login is not in this API key's scope, or the account is not tradable.` | 400 | Deliberately ambiguous: not in this key's scope, does not exist, the owner is inactive, or the account is not active. A key cannot probe for logins it does not own. **Not** returned merely because trading is disabled — that is `-2024`. | | `-2023` | `Position does not exist.` | 400 | Also returned for a position on a different login. | | `-2024` | `Trading is disabled for this account.` | 400 | The account exists and is in scope, but trading on it is forbidden. Returned by the operations that TRADE; reads such as `GET /v1/account` still succeed and report `tradeAllowed: false`. | ## `-40xx` — Filters and symbol rules The request violates a symbol specification or a group limit. Every one of these is checkable against `GET /v1/exchangeInfo` before you send. | Code | `msg` | HTTP | `mt5RetCode` | | - | - | - | - | | `-4001` | `Price less than 0.` | 400 | | | `-4002` | `Invalid price.` | 400 | 10015 | | `-4003` | `Volume less than 0.` | 400 | | | `-4004` | `Invalid volume.` | 400 | 10014 | | `-4005` | `Volume greater than volumeMax for this symbol.` | 400 | | | `-4006` | `Volume less than volumeMin for this symbol.` | 400 | | | `-4007` | `Volume is not a multiple of volumeStep for this symbol.` | 400 | | | `-4008` | `Price is not a multiple of tickSize for this symbol.` | 400 | | | `-4009` | `Stop levels are too close to market.` | 400 | 10016 | | `-4010` | `Market is closed for this symbol.` | 400 | 10018 | | `-4011` | `Trading is disabled for this symbol.` | 400 | 10017 | | `-4012` | `Invalid order expiration.` | 400 | 10022 | | `-4013` | `Only long positions are allowed for this symbol.` | 400 | 10042 | | `-4014` | `Only short positions are allowed for this symbol.` | 400 | 10043 | | `-4015` | `Only position closing is allowed for this symbol.` | 400 | 10044 | | `-4016` | `This order type is not allowed for this symbol.` | 400 | | | `-4017` | `Fill mode is not supported for this symbol.` | 400 | 10030 | | `-4018` | `Order limit reached for this account.` | 400 | 10033 | | `-4019` | `Position limit reached for this account.` | 400 | 10040 | | `-4020` | `Volume limit reached for this account.` | 400 | 10034 | | `-4021` | `Order or position is too close to market to be modified.` | 400 | 10029 | | `-4022` | `Hedge positions are prohibited for this account.` | 400 | 10046 | | `-4023` | `Position closure is not allowed by the FIFO rule.` | 400 | 10045 | | `-4024` | `Volume to close exceeds the current volume of the position.` | 400 | 10038 | | `-4025` | `An order to close this position already exists.` | 400 | 10039 | `-4005`, `-4006`, `-4007`, `-4008` and `-4009` are raised **before** the trade server is contacted where the service can determine them from the symbol specification, so they usually carry no `mt5RetCode`. The same violation caught by the server carries one. ## `-50xx` — Execution The trade server was reached and something happened during execution. These are the codes where `mt5RetCode` is normally present. | Code | `msg` | HTTP | `mt5RetCode` | Did it execute? | | - | - | - | - | - | | `-5001` | `Requote.` | 400 | 10004 | No. Re-price and resend with a new client order id. | | `-5002` | `Price has changed.` | 400 | 10020 | No. | | `-5003` | `No price available for this symbol.` | 400 | 10021 | No. | | `-5004` | `Order has been changed.` | 400 | 10023 | No — your modification raced another change. Re-read and retry. | | `-5005` | `Request does not contain changes.` | 400 | 10025 | No. | | `-5006` | `Position is already closed.` | 400 | 10036 | No — it was already gone. | | `-5007` | `The trade server rejected the request.` | 400 | 10006, 10011 | No. | | `-5008` | `The trade server did not answer in time. Execution status is UNKNOWN — confirm via the user data stream or GET /v1/order before retrying.` | 504 | 10012 | **Unknown.** | | `-5009` | `Execution status is UNKNOWN. Confirm via the user data stream or GET /v1/order before retrying.` | 503 | | **Unknown.** Retrying the same `newClientOrderId` is safe; a new one is not. | | `-5010` | `Request canceled by the trade server.` | 400 | 10007 | No. | | `-5011` | `The trade server is rejecting new requests. Retry shortly.` | 503 | 10024 | No. Back off. | | `-5012` | `Autotrading is disabled on the trade server.` | 400 | 10026, 10027 | No. Operator action required. | | `-5013` | `Request blocked by the dealer.` | 400 | 10028 | No. | | `-5014` | `Allowed only for real accounts.` | 400 | 10032 | No. | | `-5015` | `No connection to the trade server.` | 503 | 10031 | No. Retry with backoff. Also returned when a **read** cannot reach the trade server — an uncached market-data or account snapshot — in which case there is no `mt5RetCode`; and by every TRADE endpoint, `POST /v1/batchOrders` included, when the execution path is not running, before any order is created. | | `-5016` | `Invalid or prohibited order type.` | 400 | 10035 | No. | | `-5017` | `Invalid request.` | 400 | 10013 | No. | | `-5018` | `Request rejected, order canceled.` | 400 | 10041 | No — a broker routing rule cancelled it. | ### Reading `-5008` and `-5009` These two are the only codes that mean **"we do not know"**. Everything else is definitive. `-5009` also covers a failure to write the order to the execution queue: the write may have landed before the error surfaced, so the operation is recorded as still open rather than rejected, and the order can still fill. Retry it the same way as any other `-5009` — identically, same `newClientOrderId`. The same holds for an internal fault in the trade executor after the request may have been sent: the order is recorded `IN_DOUBT` with `-5009`, never as a retryable `-1000`. The safe recovery is identical for both: 1. Retry the **identical** request with the **same** `newClientOrderId`. Idempotency guarantees you get the stored outcome, not a second order. 2. Or, if you did not set a client order id, call `GET /v1/positions` and `GET /v1/userTrades` and reconcile before doing anything else. Never respond to `-5008` or `-5009` by sending the same order with a **new** client order id. That is how a position gets opened twice. ## Reserved codes These codes are part of the catalogue so a client can model them, but the current implementation does not return them: `-1000` with the "execution status is UNKNOWN" message (unknown outcomes are reported as `-5008` / `-5009`), `-1006`, `-1007`, `-1014`, `-1103`, `-1104`, `-1105`, `-1111`, `-2011` and `-2014` (an unrecognised key of any format is `-2015`). Handle them as their family describes; they may start appearing without notice. ## Quick index | Range | Family | Retry? | | - | - | - | | `-1000`, `-1006`, `-1007` | Unknown execution status | Only with the same client order id, after confirming | | `-1001`, `-1008`, `-1017` | Server-side, no execution | Yes, with backoff | | `-1003`, `-1015` | Rate limit | After `Retry-After` | | `-1002`, `-1021`, `-1022`, `-2014`, `-2015` | Authentication | No — fix credentials or clock | | `-11xx` | Malformed request | No — fix the request | | `-2013`, `-2022`, `-2023` | Not found / not in scope | No | | `-2010`, `-2018`, `-2021`, `-2024` | Rejected, did not execute | Not unchanged — fix the cause first | | `-40xx` | Symbol or group rule | No — check `GET /v1/exchangeInfo` | | `-5001` … `-5007`, `-5010` … `-5018` | Execution, definitively did not happen | Yes, as a new order | | `-5008`, `-5009` | Execution status unknown | Only with the same client order id | ================================================================================ Page: Market Data Overview URL: /market-data.md ================================================================================ # Market Data Overview Base URL `https://trade-api.yellowboxmarkets.com`. Conventions, signing and error format: [General Info](/general-info.md). Market data reflects the **trade server's** quotes for the group the symbol is served in. It is the same feed the MT5 terminal receives. There is no aggregated exchange book, no mark price and no index price. | Endpoint | Security | Weight | | - | - | - | | [`GET /v1/ping`](/market-data/test-connectivity.md) | `NONE` | 1 | | [`GET /v1/time`](/market-data/check-server-time.md) | `NONE` | 1 | | [`GET /v1/exchangeInfo`](/market-data/exchange-information.md) | `NONE` | 10 | | [`GET /v1/ticker/price`](/market-data/symbol-price-ticker.md) | `MARKET_DATA` | 1 single / 2 all | | [`GET /v1/ticker/bookTicker`](/market-data/symbol-order-book-ticker.md) | `MARKET_DATA` | 1 single / 2 all | | [`GET /v1/ticker/24hr`](/market-data/24hr-ticker-statistics.md) | `MARKET_DATA` | 1 single / 20 all | | [`GET /v1/klines`](/market-data/klines-candlestick-data.md) | `MARKET_DATA` | 1-5 by `limit` | | [`GET /v1/ticks`](/market-data/recent-ticks.md) | `MARKET_DATA` | 2 | ================================================================================ Page: Test connectivity (NONE) URL: /market-data/test-connectivity.md ================================================================================ # Test connectivity (NONE) ## API Description Tests connectivity to the REST API. Does not touch the trade server, so a successful `ping` does **not** mean trading is available — read `tradeAllowed` on `GET /v1/account` for that. ## HTTP Request ```http GET /v1/ping ``` ## Request Weight 1 ## Request Parameters NONE ## Response Example ```json {} ``` ================================================================================ Page: Check server time (NONE) URL: /market-data/check-server-time.md ================================================================================ # Check server time (NONE) ## API Description Returns the API server's clock. Use it to calibrate `timestamp` on SIGNED requests. ## HTTP Request ```http GET /v1/time ``` ## Request Weight 1 ## Request Parameters NONE ## Response Example ```json {"serverTime":1789012345678} ``` | Field | Type | Description | | - | - | - | | `serverTime` | LONG | Current server time, Unix ms. | `serverTime` is the API service's clock, which is what the `recvWindow` check uses. It is not the trade server's clock; MT5 trade timestamps may differ by a small offset. ================================================================================ Page: Exchange information (NONE) URL: /market-data/exchange-information.md ================================================================================ # Exchange information (NONE) ## API Description Trading rules, symbol specifications and the live rate limits. Call this at start-up and cache it; symbol specifications change rarely, but they do change (swap rates are commonly revised, and sessions change around holidays). Refresh at least daily. ## HTTP Request ```http GET /v1/exchangeInfo ``` ## Request Weight 10 ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `symbol` | STRING | NO | Return one symbol. Case-sensitive, exact. | | `symbols` | STRING | NO | Return several. JSON array of exact names, URL-encoded: `%5B%22XAUUSD%22,%22BTCUSD%22%5D`. | `symbol` and `symbols` are mutually exclusive (`-1128`). With neither, every symbol the trade server exposes is returned (see the note below). A name that does not exist — exact case — is `-1121`; a `symbols` value that is not a non-empty JSON array of strings is `-1130`. Symbols are returned sorted by name. This endpoint is `NONE`, but it **honours the `X-YBX-APIKEY` header when you send one**: - **Without a key:** the **default** rate limits. - **With a key:** `rateLimits[]` carrying **that key's** values. > **The symbol list is NOT filtered to your key's scope.** Every symbol the trade server exposes is > returned either way, because the Manager API offers no bulk "symbols permitted for this group" > read — only a per-symbol, per-group lookup, which would be one call per symbol per group. Treat > the list as the venue's instruments, not as your entitlements: an instrument your accounts cannot > trade is still listed and will be rejected with `-4011` on use. ## Response Example ```json { "serverTime": 1789012345678, "serverTimeZoneOffsetMinutes": 180, "rateLimits": [ {"rateLimitType": "REQUEST_WEIGHT", "interval": "MINUTE", "intervalNum": 1, "limit": 1200}, {"rateLimitType": "ORDERS", "interval": "SECOND", "intervalNum": 10, "limit": 100}, {"rateLimitType": "ORDERS", "interval": "MINUTE", "intervalNum": 1, "limit": 1200} ], "symbols": [ { "symbol": "XAUUSD", "description": "Gold vs US Dollar", "digits": 2, "tickSize": "0.01", "tickValue": "1.00", "contractSize": "100", "volumeMin": "0.01", "volumeMax": "50.00", "volumeStep": "0.01", "stopsLevel": 30, "freezeLevel": 0, "tradeMode": "FULL", "executionMode": "MARKET", "fillModes": ["FOK", "IOC"], "expirationModes": ["GTC", "DAY", "GTD", "GTD_DAY"], "orderModes": ["MARKET", "LIMIT", "STOP", "STOP_LIMIT", "SL", "TP", "CLOSE_BY"], "currencyBase": "XAU", "currencyProfit": "USD", "currencyMargin": "USD", "swapMode": "BY_POINTS", "swapLong": "-12.5", "swapShort": "4.2", "swap3Days": "WEDNESDAY", "sessions": { "SUNDAY": [], "MONDAY": [{"open": "01:05", "close": "23:55"}], "TUESDAY": [{"open": "01:05", "close": "23:55"}], "WEDNESDAY": [{"open": "01:05", "close": "23:55"}], "THURSDAY": [{"open": "01:05", "close": "23:55"}], "FRIDAY": [{"open": "01:05", "close": "23:55"}], "SATURDAY": [] } } ] } ``` **Symbol fields** | Field | Type | Description | | - | - | - | | `symbol` | STRING | Exact, case-sensitive MT5 symbol name. | | `description` | STRING | Human-readable instrument name. May be empty. | | `digits` | INT | Price decimal places. Prices are quoted and must be sent at this precision. | | `tickSize` | DECIMAL | Minimum price increment. A price that is not a multiple is rejected (`-4008`). | | `tickValue` | DECIMAL | Value of one tick per lot, in `currencyProfit`. | | `contractSize` | DECIMAL | Units of `currencyBase` per lot. | | `volumeMin` / `volumeMax` | DECIMAL | Minimum / maximum volume per order, in lots. | | `volumeStep` | DECIMAL | Volume granularity, in lots. | | `stopsLevel` | INT | Minimum distance in **points** between the market price and an SL, TP or pending price. `0` means no fixed minimum (the server still validates). See note below. | | `freezeLevel` | INT | Distance in points within which an order or position may not be modified or cancelled. `0` disables it. | | `tradeMode` | ENUM | `DISABLED`, `LONG_ONLY`, `SHORT_ONLY`, `CLOSE_ONLY`, `FULL`. | | `executionMode` | ENUM | `REQUEST`, `INSTANT`, `MARKET`, `EXCHANGE`. | | `fillModes` | ARRAY of ENUM | Allowed fill policies: `FOK`, `IOC`, `BOC`. (`RETURN` has no flag in the symbol configuration and is never listed — see [Common Definition](/common-definition.md#fill-mode).) | | `expirationModes` | ARRAY of ENUM | Allowed `timeInForce` values: `GTC`, `DAY`, `GTD`, `GTD_DAY`. | | `orderModes` | ARRAY of ENUM | Order categories allowed: `MARKET`, `LIMIT`, `STOP`, `STOP_LIMIT`, `SL`, `TP`, `CLOSE_BY`. | | `currencyBase` | STRING | Base currency / underlying. | | `currencyProfit` | STRING | Currency profit is computed in. | | `currencyMargin` | STRING | Currency margin is computed in. | | `swapMode` | ENUM | How swaps are computed — see [Common Definition](/common-definition.md#swap-mode). | | `swapLong` / `swapShort` | DECIMAL | Swap charged per lot per night on long / short positions, in the unit implied by `swapMode`. | | `swap3Days` | ENUM | Weekday on which triple swap is charged. | | `sessions` | OBJECT | Trading sessions per weekday, in **trade-server time**. Each entry is `{"open":"HH:MM","close":"HH:MM"}`. An empty array means the symbol does not trade that day. | **Notes** - `stopsLevel` is in **points**, not price units. One point is `10^-digits`, so on a `digits: 2` symbol a `stopsLevel` of `30` is 0.30 in price. Violating it is `-4009`. - `sessions` times are in the trade server's timezone. `serverTimeZoneOffsetMinutes` at the top level is that timezone's offset from UTC in minutes, so `utcMinutes = sessionMinutes - serverTimeZoneOffsetMinutes`. - `orderModes` is what the **symbol** allows. The account's group can restrict it further, and a request can still be rejected with `-4016` even when the type is listed here. - `tradeMode` other than `FULL` does not block closing an existing position, except `DISABLED`. - `rateLimits[]` is authoritative **for the key you sent** — without a key it carries the defaults. Read it at start-up rather than hard-coding the numbers from [General Info](/general-info.md#rate-limits). - `swap3Days` may also be `DISABLED` on a symbol that charges no triple swap — see [Common Definition](/common-definition.md#swap-3-days). - An MT5 value the API does not model is rendered as `UNKNOWN_` (the raw integer) in `tradeMode`, `executionMode`, `swapMode` and `swap3Days`, rather than coerced to a known value. Treat it as unrecognised. - `sessions` always carries all seven weekdays. A session that runs to midnight closes at `"24:00"`. - On the current trade server `serverTimeZoneOffsetMinutes` is `180` — see [Known Limitations](/known-limitations.md#server-time-zone). ================================================================================ Page: Symbol price ticker (MARKET_DATA) URL: /market-data/symbol-price-ticker.md ================================================================================ # Symbol price ticker (MARKET_DATA) ## API Description Latest quote for a symbol, or for every symbol. ## HTTP Request ```http GET /v1/ticker/price ``` ## Request Weight 1 with `symbol`, 2 without ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `symbol` | STRING | NO | Exact, case-sensitive. Omit for all symbols. | ## Response Example **Response** (with `symbol`) ```json {"symbol":"XAUUSD","bid":"2331.15","ask":"2331.42","last":"2331.15","time":1789012345678} ``` **Response** (without `symbol`) — an array of the same object. ```json [ {"symbol":"XAUUSD","bid":"2331.15","ask":"2331.42","last":"2331.15","time":1789012345678}, {"symbol":"BTCUSD","bid":"64120.50","ask":"64135.00","last":"64120.50","time":1789012345671} ] ``` | Field | Type | Description | | - | - | - | | `symbol` | STRING | | | `bid` | DECIMAL | Best bid. What a sell fills at. | | `ask` | DECIMAL | Best ask. What a buy fills at. | | `last` | DECIMAL | Last traded price. On many forex/CFD symbols MT5 reports `last` as `0` or equal to `bid` — the tradable prices are `bid` and `ask`. | | `time` | LONG | Quote time, Unix ms. | A symbol that is currently streamed is served from the live feed. Otherwise the single-symbol form reads the last quote from the trade server (`-5015` if it cannot be reached), and the all-symbols form takes `bid`, `ask` and `last` from the trade server's daily statistics with `time` set to the moment of the response. If the trade server cannot be reached, the all-symbols form returns only the streamed symbols — possibly an empty array — rather than an error. An unknown `symbol` is `-1121`. ================================================================================ Page: Symbol order book ticker (MARKET_DATA) URL: /market-data/symbol-order-book-ticker.md ================================================================================ # Symbol order book ticker (MARKET_DATA) ## API Description Best bid/ask. **For MT5 this is the same data as `GET /v1/ticker/price`** — the trade server's tick carries one bid and one ask with no size attached. The endpoint exists so a Binance-shaped client can call the name it expects; `bidQty` and `askQty` are always `"0"`. If you need real depth, use the `@depth` WebSocket stream, which is available on request (see [Symbol depth stream](/websocket-market-streams/symbol-depth-stream.md)). ## HTTP Request ```http GET /v1/ticker/bookTicker ``` ## Request Weight 1 with `symbol`, 2 without ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `symbol` | STRING | NO | Exact, case-sensitive. Omit for all symbols. | ## Response Example ```json {"symbol":"XAUUSD","bidPrice":"2331.15","bidQty":"0","askPrice":"2331.42","askQty":"0","time":1789012345678} ``` | Field | Type | Description | | - | - | - | | `symbol` | STRING | | | `bidPrice` | DECIMAL | Best bid. | | `bidQty` | DECIMAL | Always `"0"` — MT5 quotes carry no top-of-book size. | | `askPrice` | DECIMAL | Best ask. | | `askQty` | DECIMAL | Always `"0"`. | | `time` | LONG | Quote time, Unix ms. | ================================================================================ Page: 24hr ticker statistics (MARKET_DATA) URL: /market-data/24hr-ticker-statistics.md ================================================================================ # 24hr ticker statistics (MARKET_DATA) ## API Description Daily statistics from the trade server's tick statistics. > **This is the server's trading day, not a rolling 24-hour window.** MT5 resets these counters at > the start of each trading day in server time. The endpoint is named `24hr` for familiarity. Do > not use it to compute a trailing-24h change; build that from [klines](/market-data/klines-candlestick-data.md). ## HTTP Request ```http GET /v1/ticker/24hr ``` ## Request Weight 1 with `symbol`, 20 without ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `symbol` | STRING | NO | Exact, case-sensitive. Omit for all symbols. | ## Response Example ```json { "symbol": "XAUUSD", "openPrice": "2325.40", "highPrice": "2338.90", "lowPrice": "2321.05", "lastPrice": "2331.15", "bidPrice": "2331.15", "askPrice": "2331.42", "bidHigh": "2338.90", "bidLow": "2321.05", "askHigh": "2339.18", "askLow": "2321.30", "priceChange": "5.75", "priceChangePercent": "0.247", "volatility": "0.731", "tradeDeals": 18422, "volume": "9214.50", "buyOrders": 9611, "sellOrders": 8811, "time": 1789012345678 } ``` | Field | Type | Description | | - | - | - | | `symbol` | STRING | | | `openPrice` | DECIMAL | Day open. | | `highPrice` / `lowPrice` | DECIMAL | Day high / low, on the **bid** side. | | `lastPrice` | DECIMAL | Last traded price (see the caveat on `last` above). | | `bidPrice` / `askPrice` | DECIMAL | Current best bid / ask. | | `bidHigh` / `bidLow` / `askHigh` / `askLow` | DECIMAL | Day extremes per side. | | `priceChange` | DECIMAL | `lastPrice - openPrice`, in price units. Always derived, on both paths below. | | `priceChangePercent` | DECIMAL | Day change in percent. **Two provenances:** the trade server's own statistic when the value is read from it, and derived from `openPrice` and `lastPrice` when the symbol is currently streamed and the figure is served from the live feed (which carries no percent of its own). Expect them to agree to rounding, not bit-for-bit. | | `volatility` | DECIMAL | Day volatility in percent, as the trade server computes it. | | `tradeDeals` | LONG | Deals executed on the symbol today, broker-wide. | | `volume` | DECIMAL | Traded volume today as reported by the trade server, in its volume units (lots for most symbols), broker-wide. | | `buyOrders` / `sellOrders` | LONG | Buy / sell orders placed today, broker-wide. | | `time` | LONG | Snapshot time, Unix ms. | `tradeDeals`, `volume`, `buyOrders` and `sellOrders` are **broker-wide activity on this trade server**, not exchange volume. Treat them as an activity indicator, not as market volume, and do not assume `volume` is denominated in lots on every symbol — it is passed through in whatever unit the trade server reports. Counters are zero until the trade server has published its first statistics snapshot for the symbol after a restart. Sources follow the same rule as [`GET /v1/ticker/price`](/market-data/symbol-price-ticker.md): the live feed for a streamed symbol, otherwise the trade server's statistics (`time` is then the moment of the response). An unknown `symbol` is `-1121`; `-5015` when an unstreamed symbol's statistics cannot be read. ================================================================================ Page: Klines (candlestick data) (MARKET_DATA) URL: /market-data/klines-candlestick-data.md ================================================================================ # Klines (candlestick data) (MARKET_DATA) ## API Description Historical candles. Built by aggregating the trade server's 1-minute bars. Use this for **history**. For live candles use the [`@kline_`](/websocket-market-streams/kline-streams.md) WebSocket stream. ## HTTP Request ```http GET /v1/klines ``` ## Request Weight 1 for `limit` ≤ 100, 2 for `limit` ≤ 500, 5 for `limit` ≤ 1000 ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `symbol` | STRING | YES | Exact, case-sensitive. | | `interval` | ENUM | YES | `1m`, `5m`, `15m`, `30m`, `1h`, `2h`, `4h`, `1d`. | | `startTime` | LONG | NO | Inclusive, Unix ms. | | `endTime` | LONG | NO | Inclusive, Unix ms. | | `limit` | INT | NO | Default `500`, maximum `1000`. | - With neither `startTime` nor `endTime`, the most recent `limit` candles are returned. - With `startTime` only, up to `limit` candles **forward** from `startTime`. - With `endTime` only, the most recent `limit` candles ending at `endTime`. - With both, candles in `[startTime, endTime]`, capped at the **first** `limit` from `startTime`. - Candles are ordered **oldest first**. - The last row is the **currently forming** candle unless `endTime` is in the past. Missing `symbol` or `interval` is `-1102`; an unknown `symbol` is `-1121`; an interval not in the list (they are case-sensitive — `1M` is not `1m`) is `-1120`; a `limit` outside `1`–`1000` or a non-integer time is `-1130`. `-5015` only when the trade server cannot be reached and nothing is cached for the symbol. ## Response Example ```json [ [1789012320000, "2331.02", "2331.40", "2330.95", "2331.15", 38, 1789012379999, 0], [1789012380000, "2331.15", "2331.62", "2331.10", "2331.58", 44, 1789012439999, 0] ] ``` | Index | Name | Type | Description | | - | - | - | - | | 0 | `openTime` | LONG | Candle open, Unix ms. | | 1 | `open` | DECIMAL | | | 2 | `high` | DECIMAL | | | 3 | `low` | DECIMAL | | | 4 | `close` | DECIMAL | | | 5 | `tickVolume` | LONG | Number of ticks in the candle. This is MT5's volume for most instruments. | | 6 | `closeTime` | LONG | Last millisecond of the candle, Unix ms. | | 7 | `realVolume` | LONG | Exchange-reported traded volume. `0` on symbols where the trade server has no real volume (most forex and CFD symbols). | New elements may be appended to the end of a row without notice. Index from the front. **Notes** - Bars are **aligned to the Unix epoch in UTC**, not to the trade server's session. `1d` candles therefore open at 00:00 UTC, which is **not** the broker's trading day and will not match the daily candle in an MT5 terminal. Use `1h` or finer if you need session alignment, and build the daily bucket yourself. - `1w` and `1M` are **not supported**. Epoch-aligned bucketing does not produce correct calendar weeks or months; build them from `1d`. - The service reads 1-minute bars from the trade server and aggregates. The source window per request is bounded (currently \~50,000 minutes, about 34 days). A request whose `startTime`/`endTime` window or `limit` implies more one-minute history than that returns the most recent candles within the bound rather than an error — check `openTime` on the first row rather than assuming you received `limit` candles. - There are no gaps for weekends and market closures: candles simply do not exist for those periods. Do not assume consecutive rows are `interval` apart. ================================================================================ Page: Recent ticks (MARKET_DATA) URL: /market-data/recent-ticks.md ================================================================================ # Recent ticks (MARKET_DATA) ## API Description The most recent ticks for a symbol. > **This is not a tick-history endpoint.** Ticks are served from an in-memory ring buffer that the > service fills while it is running. It contains only ticks observed **since the service last > started**, for symbols that are currently being streamed, and it is bounded per symbol. A > restart empties it. There is no historical tick archive in this API — if you need a tick record, > capture the [`@tick`](/websocket-market-streams/tick-stream.md) stream yourself. ## HTTP Request ```http GET /v1/ticks ``` ## Request Weight 2 ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `symbol` | STRING | YES | Exact, case-sensitive. | | `limit` | INT | NO | Default `100`, range `1`–`1000` (`-1130` outside it). Fewer are returned if the buffer holds fewer. | ## Response Example **Response** — oldest first. ```json [ {"symbol":"XAUUSD","bid":"2331.14","ask":"2331.41","last":"2331.14","volume":3,"bidDirection":-1,"askDirection":-1,"time":1789012345601}, {"symbol":"XAUUSD","bid":"2331.15","ask":"2331.42","last":"2331.15","volume":8,"bidDirection":1,"askDirection":1,"time":1789012345678} ] ``` | Field | Type | Description | | - | - | - | | `symbol` | STRING | | | `bid` / `ask` / `last` | DECIMAL | | | `volume` | LONG | Tick volume reported with the quote. | | `bidDirection` / `askDirection` | INT | `1` up, `-1` down, `0` unchanged versus the previous tick. | | `time` | LONG | Tick time, Unix ms. | An empty array means the symbol is not currently streamed. Subscribing to `@tick` on the WebSocket starts the feed for that symbol; the buffer fills from that point on. ================================================================================ Page: Order Lifecycle URL: /trade.md ================================================================================ # Order Lifecycle Base URL `https://trade-api.yellowboxmarkets.com`. Conventions, signing and error format: [General Info](/general-info.md). **Every endpoint on this page takes a mandatory `login`** — the MT5 account the call applies to. See [the `login` parameter model](/general-info.md#the-login-parameter). | Endpoint | Security | Weight | Counts against `ORDERS` | | - | - | - | - | | [`POST /v1/order`](/trade/new-order.md) | `TRADE` | 1 | 1 | | [`PUT /v1/order`](/trade/modify-pending-order.md) | `TRADE` | 1 | 1 | | [`DELETE /v1/order`](/trade/cancel-pending-order.md) | `TRADE` | 1 | 1 | | [`GET /v1/order`](/trade/query-order.md) | `USER_DATA` | 1 | — | | [`GET /v1/openOrders`](/trade/current-pending-orders.md) | `USER_DATA` | 1 | — | | [`GET /v1/allOrders`](/trade/all-orders.md) | `USER_DATA` | 5 | — | | [`GET /v1/positions`](/trade/open-positions.md) | `USER_DATA` | 1 | — | | [`PUT /v1/position`](/trade/modify-position-sltp.md) | `TRADE` | 1 | 1 | | [`DELETE /v1/position`](/trade/close-position.md) | `TRADE` | 1 | 1 | | [`DELETE /v1/allOpenPositions`](/trade/close-all-open-positions.md) | `TRADE` | 5 | 1 | | [`POST /v1/batchOrders`](/trade/place-multiple-orders.md) | `TRADE` | 1 per item | 1 per item | > **Pre-release.** On the current trade server several of these operations do not yet reach the > statuses described below. Read [Known limitations](/known-limitations.md) before testing. Every `TRADE` endpoint answers `-1017` while the trading surface is switched off, and `-5015` when the execution path is not running — both before anything is queued and before the `ORDERS` bucket is charged. The `USER_DATA` reads on this page stay available in both cases. ## Order lifecycle **Read this before writing any order code.** The execution model is the one part of this API that has no Binance equivalent. ### How an order reaches the market An order is not executed inside the HTTP request. The API validates it, assigns it a client order id, and writes it to a **durable queue**. A trade executor drains that queue and issues the operation against the MT5 trade server. The fill is confirmed from the **deal record** the trade server produces. That means the HTTP response tells you how far the order got by the time we answered — not necessarily its final state. ### `newOrderRespType` | Value | Behaviour | | - | - | | `ACK` | **Default. Recommended.** Returns as soon as the order is durably queued. `status` is `ACCEPTED` for a market order and `NEW` for a pending order. | | `RESULT` | Waits up to **5 seconds** for the trade server's answer, then returns the terminal state if it arrived. | `newOrderRespType` is accepted by `POST /v1/order`, `DELETE /v1/position` and `POST /v1/batchOrders` only; any other value than `ACK` or `RESULT` is `-1130`. `PUT /v1/order`, `DELETE /v1/order` and `PUT /v1/position` take no `newOrderRespType` and **always** behave like `RESULT` — they wait up to 5 seconds and then answer with whatever status the operation has reached. `DELETE /v1/allOpenPositions` always behaves like `ACK`. With `RESULT` there are three outcomes: | Outcome | HTTP | Body | | - | - | - | | The server filled it | `200` | `status: "FILLED"` with `dealId`, `orderId`, `positionId`, `price`, `executedVolume`. | | The server rejected it | `4XX` | The error envelope plus `status: "REJECTED"`, `clientOrderId`, `login`, and `mt5RetCode`. | | No answer within 5 s | `200` | `status: "ACCEPTED"`. **The order has very likely executed.** Confirm on the user data stream or with `GET /v1/order`. | > **`RESULT` is not a guarantee of a synchronous answer.** Whether the trade server acknowledges an > operation in-band is a property of the server, not of this API. If it does not, `RESULT` behaves > exactly like `ACK` after burning 5 seconds of your latency budget. Confirm executions from the > user data stream, and treat `RESULT` as a convenience for low-rate, interactive use. > > The current trade server is such a server for every operation that creates no deal — see > [Known limitations § The trade server sends no dealer answer](/known-limitations.md#the-trade-server-sends-no-dealer-answer). Use `ACK` for bursts. One signal fanned across many accounts should be `POST /v1/batchOrders` with `ACK`; the fills arrive as `DEAL` and `POSITION_UPDATE` events. ### Status values **Market execution** (`POST /v1/order` with `type=MARKET`, `DELETE /v1/position`, `DELETE /v1/allOpenPositions`): | Status | Meaning | Terminal | | - | - | - | | `ACCEPTED` | Durably queued. Not yet confirmed by the trade server. | no | | `PARTIALLY_FILLED` | Part of the requested volume executed. `executedVolume` is what has filled so far; more `DEAL` events may follow. | no | | `FILLED` | Executed. `dealId` / `positionId` are populated and `executedVolume` equals `volume`. | yes | | `REJECTED` | The trade server refused it. `mt5RetCode` says why. | yes | | `IN_DOUBT` | The operation may or may not have reached the trade server, and the service cannot determine which. **Do not retry with a new client order id.** Check `GET /v1/positions` and the user data stream; retrying the _same_ `newClientOrderId` is safe. | no | **Pending orders** (`type` other than `MARKET`): | Status | Meaning | Terminal | | - | - | - | | `NEW` | Accepted. Reported from the moment the placement is durably queued, and while the order rests on the server. `NEW` alone does not prove the order reached the book — an `ORDER_UPDATE` with its `orderId`, or `GET /v1/openOrders`, does. | no | | `PARTIALLY_FILLED` | Part of the volume has executed; the remainder is still resting. | no | | `FILLED` | Fully executed. A position now exists. | yes | | `CANCELED` | Cancelled by you or by the broker. | yes | | `EXPIRED` | Reached its expiration. | yes | | `REJECTED` | Refused at placement. | yes | New status values may be added. Treat an unrecognised status as non-terminal and keep polling `GET /v1/order`. **`executedVolume` accumulates across deals.** MT5 may fill one order with several deals. Each one arrives as its own `DEAL` event, and `executedVolume` on `GET /v1/order` is their sum while `price` is their **volume-weighted average**. Until the sum reaches the requested `volume` the status is `PARTIALLY_FILLED`; a redelivered deal never double-counts. ### Idempotency `newClientOrderId` **is** the idempotency key. - It must be unique per API key, up to 36 characters, `[A-Za-z0-9-_.]`. - Resending a request with a `newClientOrderId` the key has already used **never creates a second order**. You get back the stored outcome — `ACCEPTED` if it is still in flight, or the terminal result if it has one. - This is what makes a retry after a timeout, a `503`, or a dropped connection safe. **A retry is answered from the stored record, not re-validated.** This matters most on the operations whose target moves: retrying a `DELETE /v1/position` whose position has since closed returns the stored `FILLED` outcome rather than `-2023`, and retrying a `PUT /v1/order` or `PUT /v1/position` whose change has already been applied returns the stored outcome rather than `-5005`. The same holds for `POST /v1/order` and every `batchOrders` item: a retried `BUY_LIMIT` whose price the market has since crossed returns its stored `NEW` (or later) outcome, not `-2021`. The response is built from the record, so a field the first response read from a live snapshot may be absent on the replay. A `newClientOrderId` this key already used for a _different kind_ of operation (an id that placed an order, sent on a close) **or for a different `login`** is refused with `-1133` on every endpoint — in a batch, as the error object in that item's slot — and is neither replayed as this operation nor executed again. **Always set it.** If you omit it the service generates one, and you lose the ability to retry safely, because a retry then looks like a brand new order — including on `DELETE /v1/position`, `PUT /v1/order`, `PUT /v1/position` and `DELETE /v1/allOpenPositions`. Idempotency records are retained for at least 24 hours. After that a reused id is treated as new. ### What to do on each failure mode | Situation | Do | | - | - | | Connection dropped, no response | Retry the identical request, same `newClientOrderId`. | | `503` with "execution status is UNKNOWN" (`-5009`) | Same: retry identically, or check `GET /v1/order?origClientOrderId=`. This also covers a failure to queue the order — it may still have been queued, so it is recorded as open, not rejected. | | `503` with `-5015` "no connection to the trade server" on a TRADE endpoint | The execution path is not running; **nothing was queued and no order exists**. Retry with backoff. | | `200` with `status: "ACCEPTED"` and no fill event after a few seconds | `GET /v1/order?origClientOrderId=`. Do not send a second order. | | `GET /v1/order` shows `REJECTED` with `-1000` "Service is currently unavailable" | The service could not queue the order within about a minute of accepting it (for example the trade executor was down), so it **expired it unexecuted** rather than send it late at a different price. Nothing executed. Retrying the **same** `newClientOrderId` sends it as a fresh order — decide against the current market first. | | `status: "IN_DOUBT"` | Reconcile against `GET /v1/positions` before doing anything else. | | `4XX` with a validation code (`-11xx`, `-40xx`) | Fix the request. The order does not exist. | | `4XX` with `-5001` (requote) or `-5002` (price changed) | The order did not execute. Re-price and send a **new** `newClientOrderId`. | ### Comments and deal attribution The MT5 `comment` field is how an execution is traced back to the request that caused it, so the service **reserves part of it**. - Your `comment` may be up to **24 characters**. - The service appends a short correlation tag, making the stored MT5 comment ``. The tag is 7 characters and is not documented as a parseable format — do not build against it. - On the user data stream, **`DEAL.c` is the MT5 comment as stored, tag included.** Strip the last 7 characters to recover what you sent, or simply ignore `c` and use `C`. - **`DEAL.C` is the resolved `clientOrderId`** — the field to route on. The service resolves it from the tag, or directly from the trade server's answer when the server provides one. - The tag is also set on **closes and SL/TP modifications** this API issues, so `DEAL.c` on an API-initiated close carries a tag too, and that close is attributable to the `newClientOrderId` you sent on `DELETE /v1/position`. A deal produced by the account holder, the broker, or a stop loss or take profit firing carries no tag. Those deals arrive with no `C`, which is how you tell them apart from your own. ### Hedging Positions are **hedged**. A `BUY` on a symbol that already has a `SELL` position opens a **second, independent position** with its own `positionId`; it does not net against the first. Closing is always by `positionId`. There is no `positionSide` parameter and no netting mode. ================================================================================ Page: New order (TRADE) URL: /trade/new-order.md ================================================================================ # New order (TRADE) ## API Description Opens a market position or places a pending order. ## HTTP Request ```http POST /v1/order ``` ## Request Weight 1 (1 against `ORDERS`) ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `login` | LONG | YES | MT5 account. | | `symbol` | STRING | YES | Exact, case-sensitive. | | `type` | ENUM | YES | `MARKET`, `BUY_LIMIT`, `SELL_LIMIT`, `BUY_STOP`, `SELL_STOP`, `BUY_STOP_LIMIT`, `SELL_STOP_LIMIT`. | | `side` | ENUM | conditional | `BUY` or `SELL`. Mandatory for `MARKET`. For every pending type the side is implied by the type; if sent it must agree, otherwise `-1117`. | | `volume` | DECIMAL | YES | Lots. Must be greater than `0` (`-4003`) and satisfy `volumeMin` (`-4006`), `volumeMax` (`-4005`) and `volumeStep` (`-4007`). | | `price` | DECIMAL | conditional | Mandatory for every pending type, greater than `0` (`-4001`). Ignored for `MARKET` — a market order always fills at the server's price. | | `stopLimitPrice` | DECIMAL | conditional | Mandatory for `BUY_STOP_LIMIT` and `SELL_STOP_LIMIT`: the limit price the order is placed at once `price` is touched. Sending it with any other `type` is `-1106`. | | `sl` | DECIMAL | NO | Stop loss. `0` or omitted means none. Negative is `-4001`. | | `tp` | DECIMAL | NO | Take profit. `0` or omitted means none. Negative is `-4001`. | | `timeInForce` | ENUM | NO | Pending orders only — sending it on `MARKET` is `-1106`. `GTC` (default), `DAY`, `GTD`, `GTD_DAY`. Must be in the symbol's `expirationModes` (`-4012`). | | `expiration` | LONG | conditional | Mandatory for `GTD` and `GTD_DAY` (`-1102`). Unix ms, greater than `0` (`-4012`). | | `newClientOrderId` | STRING | NO | **Idempotency key.** Up to 36 characters, `[A-Za-z0-9-_.]` (`-1119` otherwise), unique per key. Strongly recommended. | | `newOrderRespType` | ENUM | NO | `ACK` (default) or `RESULT`. | | `comment` | STRING | NO | Up to **24** characters (`-1130` if longer). Stored on the MT5 order and deal and visible to the broker and the account holder. The service appends a short correlation tag of its own, so the comment you see back on a deal is longer than the one you sent — see [Comments and deal attribution](/trade.md#comments-and-deal-attribution). Not a substitute for `newClientOrderId`. | | `recvWindow` | LONG | NO | Default `5000`, max `60000`. | | `timestamp` | LONG | YES | | | `signature` | STRING | YES | | **Mandatory parameters by type** | `type` | Mandatory | | - | - | | `MARKET` | `login`, `symbol`, `type`, `side`, `volume` | | `BUY_LIMIT`, `SELL_LIMIT` | `login`, `symbol`, `type`, `volume`, `price` | | `BUY_STOP`, `SELL_STOP` | `login`, `symbol`, `type`, `volume`, `price` | | `BUY_STOP_LIMIT`, `SELL_STOP_LIMIT` | `login`, `symbol`, `type`, `volume`, `price`, `stopLimitPrice` | Additionally, `expiration` is mandatory whenever `timeInForce` is `GTD` or `GTD_DAY`. **Price rules** | Type | `price` must be | | - | - | | `BUY_LIMIT` | below the current ask | | `SELL_LIMIT` | above the current bid | | `BUY_STOP` | above the current ask | | `SELL_STOP` | below the current bid | | `BUY_STOP_LIMIT` | above the current ask; `stopLimitPrice` is the resulting buy-limit price | | `SELL_STOP_LIMIT` | below the current bid; `stopLimitPrice` is the resulting sell-limit price | Violating these is `-2021`. Every price (`price`, `stopLimitPrice`, `sl`, `tp`) must be a multiple of `tickSize` (`-4008`), and must keep `stopsLevel` points of distance (`-4009`) from the price MT5 measures it against: - a pending order's `price` — from the market on its opening side (ask for a buy, bid for a sell); - `sl` / `tp` on a **market** order — from the market on its closing side (bid for a buy, ask for a sell); - `sl` / `tp` on a **pending** order — from the price the order will open at (`stopLimitPrice` for a stop-limit, `price` otherwise), not from the market. These pre-flight checks need a fresh quote; when the service has none, the trade server performs them instead and the error carries its `mt5RetCode`. The symbol's rules are checked too: `tradeMode` (`-4011` disabled, `-4013` long only, `-4014` short only, `-4015` close only), `orderModes` (`-4016` for a `type` — or an `sl` / `tp` — the symbol does not allow) and `expirationModes` (`-4012`). An unknown `symbol` is `-1121`. A retry with a `newClientOrderId` this key already used skips all of this and returns the stored outcome — see [Idempotency](/trade.md#idempotency). ## Response Example **Response — `ACK` (default)** ```json { "login": 100123, "clientOrderId": "bot-001", "symbol": "XAUUSD", "side": "BUY", "type": "MARKET", "volume": "0.10", "status": "ACCEPTED", "transactTime": 1789012345690 } ``` **Response — `RESULT`, market order filled** ```json { "login": 100123, "clientOrderId": "bot-001", "symbol": "XAUUSD", "side": "BUY", "type": "MARKET", "volume": "0.10", "status": "FILLED", "orderId": 44412345, "dealId": 55512345, "positionId": 44412345, "price": "2331.42", "executedVolume": "0.10", "sl": "2320.00", "tp": "2350.00", "mt5RetCode": 10009, "transactTime": 1789012345812 } ``` **Response — pending order placed** (`RESULT`) ```json { "login": 100123, "clientOrderId": "bot-002", "symbol": "XAUUSD", "side": "BUY", "type": "BUY_LIMIT", "volume": "0.10", "status": "NEW", "orderId": 44412350, "price": "2325.00", "timeInForce": "GTC", "mt5RetCode": 10008, "transactTime": 1789012345812 } ``` **Response — rejected** (HTTP `400`) ```json { "code": -2018, "msg": "Insufficient margin on the trading account.", "mt5RetCode": 10019, "status": "REJECTED", "clientOrderId": "bot-003", "login": 100123 } ``` When the order was recorded and then rejected, the error body carries `status`, `clientOrderId` and `login` in addition to the standard envelope, so the rejection can be correlated without a second lookup. A request refused **before** it was recorded — a validation error such as `-1102`, `-4007` or `-2022` — returns the plain `{"code","msg"}` envelope. Fields whose value is unknown or does not apply are **omitted**, not sent as `null`: an `ACK` response carries no `orderId`, `dealId`, `positionId`, `price` or `executedVolume`, and `sl`, `tp` and `expiration` are absent when the order has none. **Response fields** | Field | Type | Description | | - | - | - | | `login` | LONG | | | `clientOrderId` | STRING | Yours if you sent one, otherwise generated. | | `symbol` | STRING | | | `side` | ENUM | `BUY` / `SELL`. Derived from `type` for pending orders. | | `type` | ENUM | As sent. | | `volume` | DECIMAL | Requested volume, lots. | | `status` | ENUM | See [Status values](/trade.md#status-values). | | `orderId` | LONG | MT5 order ticket. Absent until the server assigns one. | | `dealId` | LONG | MT5 deal ticket. Market fills only. | | `positionId` | LONG | MT5 position ticket. Market fills only. Under hedging it is typically equal to `orderId` for a newly opened position, but **do not rely on that** — use the value returned. | | `price` | DECIMAL | Fill price for a market order; order price for a pending order. | | `executedVolume` | DECIMAL | Filled volume, lots. May be less than `volume` on a partial fill. | | `sl` / `tp` | DECIMAL | As sent. Absent when none was set. | | `timeInForce` | ENUM | Pending orders only. | | `expiration` | LONG | Pending orders only, Unix ms. Absent when none was set. | | `mt5RetCode` | INT | The trade server's raw return code, when it answered. | | `transactTime` | LONG | When the API produced this response, Unix ms. | ================================================================================ Page: Modify pending order (TRADE) URL: /trade/modify-pending-order.md ================================================================================ # Modify pending order (TRADE) ## API Description Changes a resting pending order. Cannot modify a market order (there is nothing to modify) or a position (use [`PUT /v1/position`](/trade/modify-position-sltp.md)). ## HTTP Request ```http PUT /v1/order ``` ## Request Weight 1 (1 against `ORDERS`) ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `login` | LONG | YES | | | `orderId` | LONG | conditional | The pending order ticket. Send this or `origClientOrderId`. Any pending order on the login can be addressed by ticket, including one this API did not place. | | `origClientOrderId` | STRING | conditional | The `newClientOrderId` the order was placed with. See [Known limitations](/known-limitations.md#modify--cancel--query-by-origclientorderid-does-not-find-a-pending-order). | | `price` | DECIMAL | NO | New trigger price. | | `stopLimitPrice` | DECIMAL | NO | New limit price. Stop-limit orders only — `-1106` on any other order. | | `sl` | DECIMAL | NO | New stop loss. `0` clears. | | `tp` | DECIMAL | NO | New take profit. `0` clears. | | `timeInForce` | ENUM | conditional | New expiration mode. **Mandatory when the service cannot determine the order's current one** — see below. | | `expiration` | LONG | conditional | New expiration, Unix ms. Mandatory if `timeInForce` is or becomes `GTD` or `GTD_DAY` and the current value is not determinable. | | `newClientOrderId` | STRING | NO | Idempotency key **for this modification**. Must differ from the original order's id. | | `recvWindow`, `timestamp`, `signature` | | | | Exactly one of `orderId` / `origClientOrderId` (`-1128` if both or neither). An identifier that does not resolve to an order currently resting on this `login` is `-2013`. At least one field to change (`-5005` if the request contains no changes). On a **stop-limit** order the service cannot always read back both current prices; if either is undeterminable and you did not send it, the request is `-1102` — send both `price` and `stopLimitPrice` when modifying a stop-limit order. > **Omitted fields are left unchanged.** The service reads the order's current values and writes > them back alongside your changes. Send `0` to clear an `sl` or `tp`; omitting it preserves it. > **`timeInForce` may be mandatory.** MT5 rewrites every field of an order in one operation, > including its expiration mode, so a modify always sends one. When you omit `timeInForce` the > service uses, in order: the value **it last sent for this ticket** (from the order's own placement > or an earlier modification through this API), then the trade server's own value where the order > record carries one. If neither can answer — typically an order this API did not place — the > request is refused with `-1102` rather than guessing, because a guess would silently turn a `DAY` > order into a `GTC` one. Send `timeInForce` explicitly in that case. The same rule applies to > `expiration` when the resolved mode requires one. ## Response Example ```json { "login": 100123, "clientOrderId": "bot-002-mod-1", "origClientOrderId": "bot-002", "orderId": 44412350, "symbol": "XAUUSD", "side": "BUY", "type": "BUY_LIMIT", "volume": "0.10", "status": "NEW", "price": "2323.00", "sl": "2315.00", "timeInForce": "GTC", "mt5RetCode": 10009, "transactTime": 1789012400112 } ``` The fields are those of [`POST /v1/order`](/trade/new-order.md) plus `origClientOrderId`, the id the order was placed with (absent for an order this API did not place). `clientOrderId` is this modification's own id — yours, or a generated one. `status` stays `NEW` while the order rests. An order inside `freezeLevel` of the market cannot be modified (`-4021`). Prices must be multiples of `tickSize` (`-4008`). ================================================================================ Page: Cancel pending order (TRADE) URL: /trade/cancel-pending-order.md ================================================================================ # Cancel pending order (TRADE) ## HTTP Request ```http DELETE /v1/order ``` ## Request Weight 1 (1 against `ORDERS`) ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `login` | LONG | YES | | | `orderId` | LONG | conditional | Send this or `origClientOrderId` (`-1128` if both or neither). | | `origClientOrderId` | STRING | conditional | The `newClientOrderId` the order was placed with. See [Known limitations](/known-limitations.md#modify--cancel--query-by-origclientorderid-does-not-find-a-pending-order). | | `newClientOrderId` | STRING | NO | Idempotency key **for this cancel**. A retry with the same id returns the stored outcome instead of `-2013`. | | `recvWindow`, `timestamp`, `signature` | | | | ## Response Example ```json { "login": 100123, "clientOrderId": "bot-002-cancel", "origClientOrderId": "bot-002", "orderId": 44412350, "symbol": "XAUUSD", "side": "BUY", "type": "BUY_LIMIT", "volume": "0.10", "status": "CANCELED", "mt5RetCode": 10009, "transactTime": 1789012400500 } ``` `clientOrderId` is this cancel's own id (yours, or a generated one); `origClientOrderId` is the id the order was placed with. The call waits up to 5 seconds for the trade server and answers `ACCEPTED` if the cancel is not confirmed by then. Cancelling an order that does not exist, has already filled, or has already been cancelled is `-2013`. Without a `newClientOrderId`, cancelling is idempotent in effect but not in reporting: the second call reports `-2013`, not `CANCELED`. > **Pre-release:** cancels currently fail on the trade server — see > [Known limitations](/known-limitations.md#cancelling-a-pending-order-currently-fails-on-the-trade-server). ================================================================================ Page: Query order (USER_DATA) URL: /trade/query-order.md ================================================================================ # Query order (USER_DATA) ## API Description The current state of one order. This is the endpoint to poll after an `ACCEPTED` response. For a **market** order it returns the execution record, including `dealId` and `positionId` once the fill is known. For a **pending** order it returns the order, whether resting, filled, cancelled or expired. ## HTTP Request ```http GET /v1/order ``` ## Request Weight 1 ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `login` | LONG | YES | | | `orderId` | LONG | conditional | Send this or `origClientOrderId`. | | `origClientOrderId` | STRING | conditional | The `newClientOrderId` used when placing. Works even before the trade server has assigned an `orderId`. For a pending order on the current server, see [Known limitations](/known-limitations.md#modify--cancel--query-by-origclientorderid-does-not-find-a-pending-order). | | `recvWindow`, `timestamp`, `signature` | | | | ## Response Example ```json { "login": 100123, "clientOrderId": "bot-001", "orderId": 44412345, "dealId": 55512345, "positionId": 44412345, "symbol": "XAUUSD", "side": "BUY", "type": "MARKET", "status": "FILLED", "volume": "0.10", "executedVolume": "0.10", "price": "2331.42", "stopLimitPrice": "0", "sl": "2320.00", "tp": "2350.00", "timeInForce": "GTC", "expiration": 0, "comment": "signal-7", "mt5RetCode": 10009, "time": 1789012345690, "updateTime": 1789012345812 } ``` | Field | Type | Description | | - | - | - | | `time` | LONG | When the order was accepted by this API, Unix ms. | | `updateTime` | LONG | Last state change, Unix ms. | | `orderId` | LONG | Absent until the trade server has assigned one. | | `clientOrderId` | STRING | `""` for an order this API did not place (found by `orderId`). | | everything else | | as on [`POST /v1/order`](/trade/new-order.md), except that `stopLimitPrice`, `sl`, `tp`, `timeInForce` and `expiration` are always present — `"0"` / `0` meaning none. | Exactly one of `orderId` / `origClientOrderId` (`-1128` if both or neither). `-2013` if neither identifier resolves for that `login`. An order belonging to a different login is also `-2013` — never a permission error. Orders are queryable by `origClientOrderId` for at least 24 hours and by `orderId` for as long as the trade server retains them in history. ================================================================================ Page: Current pending orders (USER_DATA) URL: /trade/current-pending-orders.md ================================================================================ # Current pending orders (USER_DATA) ## API Description Every pending order currently resting on the account. Market orders never appear here — once filled they are positions. ## HTTP Request ```http GET /v1/openOrders ``` ## Request Weight 1 ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `login` | LONG | YES | | | `symbol` | STRING | NO | Filter to one symbol. Exact, case-sensitive. | | `recvWindow`, `timestamp`, `signature` | | | | ## Response Example ```json [ { "login": 100123, "orderId": 44412350, "clientOrderId": "bot-002", "symbol": "XAUUSD", "side": "BUY", "type": "BUY_LIMIT", "status": "NEW", "volume": "0.10", "remainingVolume": "0.10", "price": "2325.00", "stopLimitPrice": "0", "sl": "2315.00", "tp": "0", "timeInForce": "GTC", "expiration": 0, "comment": "", "time": 1789012345690, "updateTime": 1789012345690 } ] ``` | Field | Type | Description | | - | - | - | | `volume` | DECIMAL | Initial volume, lots. | | `remainingVolume` | DECIMAL | Volume still resting, lots. Less than `volume` after a partial fill. | `clientOrderId` is `""` for orders this API did not place — the account holder and the broker can place orders too, and they appear here. ================================================================================ Page: All orders (USER_DATA) URL: /trade/all-orders.md ================================================================================ # All orders (USER_DATA) ## API Description Order history, including filled, cancelled and expired orders. ## HTTP Request ```http GET /v1/allOrders ``` ## Request Weight 5 ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `login` | LONG | YES | | | `symbol` | STRING | NO | Exact, case-sensitive. | | `orderId` | LONG | NO | Return orders with an id **greater than or equal to** this, within the time window. Order history is read from the trade server, which is always windowed — unlike `userTrades`, whose `fromId` overrides its window. | | `startTime` | LONG | NO | Inclusive, Unix ms. | | `endTime` | LONG | NO | Inclusive, Unix ms. | | `limit` | INT | NO | Default `500`, maximum `1000`. | | `recvWindow`, `timestamp`, `signature` | | | | - With no window, the last **7 days** are returned. `endTime` alone returns the 7 days ending at `endTime`; `startTime` alone runs from `startTime` to now. - `endTime - startTime` may not exceed **7 days** (`-1127`) — including the implicit "now" when only `startTime` is sent. `endTime` before `startTime` is `-1128`. - Ordered oldest first, by setup time. - `-5015` if the trade server's order history cannot be read. ## Response Example **Response** — an array of the [`GET /v1/openOrders`](/trade/current-pending-orders.md) object, with terminal statuses present: ```json [ { "login": 100123, "orderId": 44412350, "clientOrderId": "bot-002", "symbol": "XAUUSD", "side": "BUY", "type": "BUY_LIMIT", "status": "CANCELED", "volume": "0.10", "remainingVolume": "0.10", "price": "2325.00", "stopLimitPrice": "0", "sl": "2315.00", "tp": "0", "timeInForce": "GTC", "expiration": 0, "comment": "", "time": 1789012345690, "updateTime": 1789012400500 } ] ``` ================================================================================ Page: Open positions (USER_DATA) URL: /trade/open-positions.md ================================================================================ # Open positions (USER_DATA) ## API Description Every open position on the account. Under hedging a symbol can hold many, in both directions. ## HTTP Request ```http GET /v1/positions ``` ## Request Weight 1 ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `login` | LONG | YES | | | `symbol` | STRING | NO | Exact, case-sensitive. | | `recvWindow`, `timestamp`, `signature` | | | | ## Response Example ```json [ { "login": 100123, "positionId": 44412345, "symbol": "XAUUSD", "side": "BUY", "volume": "0.10", "priceOpen": "2331.42", "priceCurrent": "2331.15", "sl": "2320.00", "tp": "2350.00", "profit": "-2.7000", "swap": "0.0000", "openTime": 1789012345000, "updateTime": 1789012400000, "comment": "signal-7" } ] ``` | Field | Type | Description | | - | - | - | | `positionId` | LONG | MT5 position ticket. The handle for closing and for SL/TP changes. | | `side` | ENUM | `BUY` / `SELL`. | | `volume` | DECIMAL | Open volume, lots. Reduced by a partial close. | | `priceOpen` | DECIMAL | Volume-weighted open price. | | `priceCurrent` | DECIMAL | The trade server's current price for the position. | | `profit` | DECIMAL | **Floating** profit, as the trade server computes it — spread, conversion and the symbol's contract terms included. In the account's deposit currency. | | `swap` | DECIMAL | Swap accumulated on this position so far. | | `openTime` / `updateTime` | LONG | Unix ms. | | `comment` | STRING | The comment the position was opened with, as stored on MT5. For a position opened through this API that includes the service's correlation tag — see [Comments and deal attribution](/trade.md#comments-and-deal-attribution). | > **`profit` refreshes on a server-side cadence of a few seconds, not on every tick.** It is the > trade server's own number, which is why it is authoritative and why it is not tick-fresh. If you > need per-tick mark-to-market, compute it yourself from the `@tick` stream and > `priceOpen`; use `profit` for anything that has to agree with the account statement. > > `profit` excludes `swap`, and **commission is not carried on a position** — it is booked on the > deals. Read it from [`GET /v1/userTrades`](/account/account-trade-list.md) or the `DEAL` event's > `n` field, and account for it per deal rather than per position. ================================================================================ Page: Modify position SL/TP (TRADE) URL: /trade/modify-position-sltp.md ================================================================================ # Modify position SL/TP (TRADE) ## API Description Sets the stop loss and take profit on an open position. ## HTTP Request ```http PUT /v1/position ``` ## Request Weight 1 (1 against `ORDERS`) ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `login` | LONG | YES | | | `positionId` | LONG | YES | | | `sl` | DECIMAL | NO | New stop loss. Send `0` to clear. Omit to leave unchanged. | | `tp` | DECIMAL | NO | New take profit. Send `0` to clear. Omit to leave unchanged. | | `newClientOrderId` | STRING | NO | Idempotency key for this modification. | | `recvWindow`, `timestamp`, `signature` | | | | At least one of `sl` / `tp` (`-1102` otherwise). A request that would change nothing is `-5005`. `-2023` if the position does not exist on this `login`. A negative level is `-4001`. > **`0` clears, omission preserves.** MT5 writes both levels in a single operation, so the service > reads the position's current values and sends them back with your change applied. Never send > `sl=0` meaning "leave it alone". Both levels must be multiples of `tickSize` (`-4008`) and keep `stopsLevel` points from the market on the position's closing side — bid for a `BUY` position, ask for a `SELL` (`-4009`). A level being changed that currently sits inside `freezeLevel` of that price cannot be modified (`-4021`). ## Response Example ```json { "login": 100123, "positionId": 44412345, "clientOrderId": "bot-sltp-1", "symbol": "XAUUSD", "side": "BUY", "volume": "0.10", "sl": "2325.00", "tp": "2350.00", "status": "FILLED", "mt5RetCode": 10009, "transactTime": 1789012500110 } ``` `status` is `FILLED` when the trade server confirmed the change, `ACCEPTED` when it is still queued after the 5-second wait (see [Known limitations](/known-limitations.md#the-trade-server-sends-no-dealer-answer)). `clientOrderId` is this modification's id — yours, or a generated one. `sl` / `tp` are the levels sent, `"0"` meaning none. `volume` is the position's volume when the change was made; it may be absent on an idempotent replay. ================================================================================ Page: Close position (TRADE) URL: /trade/close-position.md ================================================================================ # Close position (TRADE) ## API Description Closes an open position, fully or partially, at market. ## HTTP Request ```http DELETE /v1/position ``` ## Request Weight 1 (1 against `ORDERS`) ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `login` | LONG | YES | | | `positionId` | LONG | YES | | | `volume` | DECIMAL | NO | Lots to close. Omit to close the whole position. Must be greater than `0` (`-4003`), ≤ the position's current volume (`-4024`) and, for a partial close, a multiple of `volumeStep` (`-4007`). A full close is never refused for its step. | | `newClientOrderId` | STRING | NO | **Idempotency key.** Strongly recommended — a retried close without one can close the position twice if it was reopened in between. | | `newOrderRespType` | ENUM | NO | `ACK` (default) or `RESULT`. | | `recvWindow`, `timestamp`, `signature` | | | | A partial close leaves the position open with the remaining volume and the **same** `positionId`. ## Response Example **Response** (`RESULT`) ```json { "login": 100123, "positionId": 44412345, "clientOrderId": "bot-close-1", "symbol": "XAUUSD", "side": "SELL", "volume": "0.10", "status": "FILLED", "orderId": 44412399, "dealId": 55512400, "price": "2331.60", "executedVolume": "0.10", "mt5RetCode": 10009, "transactTime": 1789012600410 } ``` `side` on the response is the side of the **closing deal** — the opposite of the position's side. `volume` is the volume you asked to close; `executedVolume` and `price` (volume-weighted) are what filled. With `ACK`, or while the close is unconfirmed, `orderId`, `dealId`, `price` and `executedVolume` are absent. The realised profit of the close is **not** on this response — the trade server books it on the deal. Read it from the `DEAL` event's `rp` field or from [`GET /v1/userTrades`](/account/account-trade-list.md). `-2023` if the position does not exist or belongs to another login. `-5006` if it was already closed (for example by its own stop loss) between your read and your close. ================================================================================ Page: Close all open positions (TRADE) URL: /trade/close-all-open-positions.md ================================================================================ # Close all open positions (TRADE) ## API Description Closes every open position on the account, optionally filtered to one symbol. Pending orders are **not** cancelled — use [`DELETE /v1/order`](/trade/cancel-pending-order.md) for those. ## HTTP Request ```http DELETE /v1/allOpenPositions ``` ## Request Weight 5 (1 against `ORDERS`, regardless of how many positions close) ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `login` | LONG | YES | | | `symbol` | STRING | NO | Close only this symbol's positions. Exact, case-sensitive. | | `newClientOrderId` | STRING | NO | Idempotency key for the whole sweep. | | `recvWindow`, `timestamp`, `signature` | | | | > **Omitting `symbol` closes every open position on the account, on every symbol.** There is no > confirmation step. Send `symbol` unless you mean to flatten the account. The sweep snapshots the open positions at the moment it runs and closes those. A position opened after the snapshot is not closed. It is not a "keep the account flat" mode. > **A retry replays the ORIGINAL snapshot.** Resending with the same `newClientOrderId` returns the > legs of the first sweep and closes nothing that was opened afterwards — including when the first > sweep found nothing and answered `requested: 0`, which stays `0` on every repeat. To sweep again, > send a **fresh** `newClientOrderId`. Legs the service could not confirm as queued are re-queued > under their own existing keys, so a retry is safe without being a second sweep. ## Response Example **Response** — one entry per position it attempted to close. ```json { "login": 100123, "clientOrderId": "bot-flatten-1", "requested": 2, "status": "ACCEPTED", "transactTime": 1789012700100, "positions": [ {"positionId": 44412345, "symbol": "XAUUSD", "volume": "0.10", "status": "ACCEPTED"}, {"positionId": 44412360, "symbol": "BTCUSD", "volume": "0.05", "status": "ACCEPTED"} ] } ``` | Field | Type | Description | | - | - | - | | `requested` | INT | Positions found in the snapshot. `0` with an empty `positions` array when the account is already flat — this is a success, not an error. | | `status` | ENUM | `ACCEPTED` once every close is queued. Per-position outcomes arrive on the user data stream. | | `positions[].status` | ENUM | Per-position status, same values as a single close. | ================================================================================ Page: Place multiple orders (TRADE) URL: /trade/place-multiple-orders.md ================================================================================ # Place multiple orders (TRADE) ## API Description The burst primitive. Up to 100 orders in one signed request, **each with its own `login`** — this is how you fan one signal out across many accounts in a single round trip. ## HTTP Request ```http POST /v1/batchOrders ``` ## Request Weight 1 per item (1 per item against `ORDERS`) ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `batchOrders` | LIST\ | YES | JSON array of 1-100 order objects. URL-encode it as a single form field value. | | `newOrderRespType` | ENUM | NO | Applies to every item. `ACK` (default) or `RESULT`. | | `recvWindow`, `timestamp`, `signature` | | | | Each item takes the same fields as [`POST /v1/order`](/trade/new-order.md), **including its own `login`**: `login`, `symbol`, `type`, `side`, `volume`, `price`, `stopLimitPrice`, `sl`, `tp`, `timeInForce`, `expiration`, `newClientOrderId`, `comment` (24 characters, same reserved-tag rule). Items do not carry `timestamp`, `signature`, `recvWindow` or `newOrderRespType` — those are request-level. > Sign the body **exactly as transmitted**, including the percent-encoded `batchOrders` value. Build > the encoded string once, sign that string, send that string. Re-serialising the JSON after signing > changes the bytes and gives `-1022`. The whole batch is refused, with nothing queued, when `batchOrders` is missing, not an array, empty or longer than 100 items (`-1131`), is not valid JSON or contains an element that is not an object (`-1130`), or repeats a `newClientOrderId` (`-1132`). Everything else — a missing or invalid item `login` (`-1102` / `-1122`), a login outside the key's scope (`-2022`), any validation or execution error — is reported per item, in that item's slot. `RESULT` on a large batch holds the connection for up to 5 seconds. Use `ACK` for anything latency-sensitive. **Example body** (before URL-encoding) ```json [ {"login":100123,"symbol":"XAUUSD","type":"MARKET","side":"BUY","volume":"0.10","newClientOrderId":"sig7-100123"}, {"login":100124,"symbol":"XAUUSD","type":"MARKET","side":"BUY","volume":"0.25","newClientOrderId":"sig7-100124"}, {"login":100125,"symbol":"XAUUSD","type":"MARKET","side":"BUY","volume":"0.05","newClientOrderId":"sig7-100125"} ] ``` ## Response Example **Response** — an array **in the same order as the request**. Each element is either a success object (the same shape as a single order response) or an error object. A failed item does not affect the others; the batch is **not** atomic. ```json [ { "login": 100123, "clientOrderId": "sig7-100123", "symbol": "XAUUSD", "side": "BUY", "type": "MARKET", "volume": "0.10", "status": "ACCEPTED", "transactTime": 1789012800100 }, { "code": -2018, "msg": "Insufficient margin on the trading account.", "mt5RetCode": 10019, "status": "REJECTED", "clientOrderId": "sig7-100124", "login": 100124 }, { "code": -2022, "msg": "Login is not in this API key's scope, or the account is not tradable.", "login": 100125 } ] ``` Match results to requests **by index**, or by `clientOrderId` — which is why each item should carry a distinct one. The HTTP status is `200` whenever the batch itself was accepted, even if every item failed. A `4XX` on the batch means the batch was rejected as a whole (bad signature, malformed array, over 100 items) and **no** item was queued. ================================================================================ Page: Account Overview URL: /account.md ================================================================================ # Account Overview Base URL `https://trade-api.yellowboxmarkets.com`. Conventions, signing and error format: [General Info](/general-info.md). | Endpoint | Security | Weight | | - | - | - | | [`GET /v1/account`](/account/account-information.md) | `USER_DATA` | 1 | | [`GET /v1/accounts`](/account/accounts-in-key-scope.md) | `USER_DATA` | 5 | | [`GET /v1/userTrades`](/account/account-trade-list.md) | `USER_DATA` | 5 | All money values are in the account's deposit currency (`currency`). Read [the cent-account note](/general-info.md#money-is-in-the-accounts-deposit-currency) before aggregating across accounts. ================================================================================ Page: Account information (USER_DATA) URL: /account/account-information.md ================================================================================ # Account information (USER_DATA) ## API Description Balance, equity and margin for one MT5 account, as the trade server reports them. ## HTTP Request ```http GET /v1/account ``` ## Request Weight 1 ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `login` | LONG | YES | MT5 account. Must be in the key's scope (`-2022`). An account whose trading is disabled is still READABLE — it answers normally with `tradeAllowed: false`. | | `recvWindow` | LONG | NO | Default `5000`, max `60000`. | | `timestamp` | LONG | YES | | | `signature` | STRING | YES | | ## Response Example ```json { "login": 100123, "group": "real\\BOT\\ECN", "currency": "USD", "leverage": 100, "balance": "10000.0000", "credit": "0.0000", "equity": "10012.3400", "margin": "233.1200", "freeMargin": "9779.2200", "marginLevel": "4294.60", "profit": "12.3400", "tradeAllowed": true, "updateTime": 1789012345678 } ``` | Field | Type | Description | | - | - | - | | `login` | LONG | | | `group` | STRING | MT5 group the account belongs to. Determines symbols, leverage, commissions and whether the account is a cent account. Backslashes are JSON-escaped. | | `currency` | STRING | Deposit currency. `USC` means a **cent** account — see the note above. | | `leverage` | INT | Account leverage, e.g. `100` for 1:100. `0` if the trade server could not be asked for it on this read. | | `balance` | DECIMAL | Realised balance. Does not include floating profit. | | `credit` | DECIMAL | Non-withdrawable credit granted by the broker (bonus or promotional credit). It contributes to margin but is not your money. | | `equity` | DECIMAL | `balance + credit + floating profit`. | | `margin` | DECIMAL | Margin currently used by open positions. | | `freeMargin` | DECIMAL | Margin available for new positions. | | `marginLevel` | DECIMAL | `equity / margin * 100`, in percent, two decimals. `"0.00"` when no position is open. | | `profit` | DECIMAL | Total floating profit across open positions. Excludes swap and commission. | | `tradeAllowed` | BOOLEAN | Whether trading is currently permitted on this account — `false` when the broker has disabled trading on the account **or** the trade server reports trading disabled for it. In the first case every TRADE call answers `-2024`; in the second the order reaches the trade server and is rejected there. Either way this endpoint keeps answering. | | `updateTime` | LONG | When this snapshot was taken, Unix ms. | > **`equity` and `margin` are read from the trade server, not derived.** The service caches them > very briefly (order of seconds) to protect the trade server from polling. Do not use this endpoint > as a tick-by-tick equity feed — build that from `GET /v1/positions` plus the tick stream, or from > `ACCOUNT_UPDATE` plus `POSITION_UPDATE` on the user data stream. > > Note that `ACCOUNT_UPDATE` on the user data stream carries **only** `balance` and `credit`. Equity > and margin are not on that event; this endpoint is the way to get them. **Margin call and stop out.** Those levels are group settings on the trade server and are not exposed by this API. Track `marginLevel` yourself and ask the broker for the group's thresholds. ================================================================================ Page: Accounts in key scope (USER_DATA) URL: /account/accounts-in-key-scope.md ================================================================================ # Accounts in key scope (USER_DATA) ## API Description Every MT5 login this API key may trade. Call it at start-up; the set changes only when the broker changes it. ## HTTP Request ```http GET /v1/accounts ``` ## Request Weight 5 ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `recvWindow` | LONG | NO | | | `timestamp` | LONG | YES | | | `signature` | STRING | YES | | ## Response Example ```json [ {"login": 100123, "group": "real\\BOT\\ECN", "currency": "USD", "demo": false, "tradeAllowed": true}, {"login": 100124, "group": "real\\BOT\\CENT", "currency": "USC", "demo": false, "tradeAllowed": true}, {"login": 500901, "group": "demo\\BOT\\ECN", "currency": "USD", "demo": true, "tradeAllowed": true} ] ``` | Field | Type | Description | | - | - | - | | `login` | LONG | | | `group` | STRING | MT5 group. | | `currency` | STRING | Deposit currency. `USC` = cent account. | | `demo` | BOOLEAN | `true` for a demo account. A key scoped exclusively to `demo: true` logins is a sandbox key. | | `tradeAllowed` | BOOLEAN | Whether the broker permits trading on the account (`false` ⇒ TRADE calls answer `-2024`). Unlike `GET /v1/account`, this does not ask the trade server, so it does not reflect a trading block set on the trade server itself. | No balances here — that would make this endpoint expensive. Call [`GET /v1/account`](/account/account-information.md) per login for those. An empty array means the key has no accounts assigned yet. Every subsequent account-scoped call will return `-2022`. The list is exactly the set of logins every other endpoint accepts for this key: an account that is closed or archived on the broker's side is not listed, answers `-2022` everywhere, and is not carried on the user data stream. An account whose trading is merely disabled IS listed, with `tradeAllowed: false` — it stays readable. ================================================================================ Page: Account trade list (USER_DATA) URL: /account/account-trade-list.md ================================================================================ # Account trade list (USER_DATA) ## API Description Executed deals for the account. A deal is the atomic execution record: a market entry, an exit, a partial close, or a balance-affecting operation. > **Today this endpoint returns CLOSING deals only.** It reads the broker's stored deal history, and > that history currently persists only the deals that close or reduce a position — so `entry` is > `OUT`, `INOUT` or `OUT_BY`, never `IN`. The opening deal of a round trip does not appear here. > Realised P\&L still reconciles, because profit, commission and swap are booked on the exit; what you > cannot reconstruct from this endpoint alone is the entry price, which is on the `DEAL` event > ([user data stream](/user-data-streams/deal.md)) at the moment of the fill. ## HTTP Request ```http GET /v1/userTrades ``` ## Request Weight 5 ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `login` | LONG | YES | | | `symbol` | STRING | NO | Exact, case-sensitive. | | `startTime` | LONG | NO | Inclusive, Unix ms. | | `endTime` | LONG | NO | Inclusive, Unix ms. | | `fromId` | LONG | NO | Return deals with a `dealId` greater than or equal to this. Overrides the time window. | | `limit` | INT | NO | Default `500`, maximum `1000`. | | `recvWindow`, `timestamp`, `signature` | | | | - With no window, the last **7 days** are returned. `endTime` alone returns the 7 days ending at `endTime`; `startTime` alone runs from `startTime` to now. - `endTime - startTime` may not exceed **7 days** (`-1127`) — including the implicit "now" when only `startTime` is sent. `endTime` before `startTime` is `-1128`. - The window applies to the deal's close time. `fromId` ignores it entirely. - Ordered oldest first, by `dealId`. > **Pre-release:** this endpoint returns `[]` on staging — see > [Known limitations](/known-limitations.md#get-v1usertrades-is-empty-on-staging). ## Response Example ```json [ { "login": 100123, "dealId": 55512400, "orderId": 44412399, "positionId": 44412345, "symbol": "XAUUSD", "side": "SELL", "entry": "OUT", "volume": "0.10", "price": "2331.60", "profit": "1.8000", "commission": "-0.7000", "swap": "0.0000", "comment": "signal-7", "time": 1789012600000 }, { "login": 100123, "dealId": 55512455, "orderId": 44412450, "positionId": 44412400, "symbol": "BTCUSD", "side": "BUY", "entry": "OUT", "volume": "0.05", "price": "64135.00", "profit": "-12.5000", "commission": "-0.4000", "swap": "-0.1200", "comment": "", "time": 1789012900000 } ] ``` | Field | Type | Description | | - | - | - | | `dealId` | LONG | Deal ticket. The natural key — **dedupe on it.** | | `orderId` | LONG | Order that produced the deal. | | `positionId` | LONG | Position the deal opened, added to, or closed. | | `side` | ENUM | `BUY` / `SELL`, **as the broker's history writer stored it.** See the caveat below — do not assume it is the deal's own direction. | | `entry` | ENUM | `OUT` closing or partially closing, `INOUT` close-and-reverse, `OUT_BY` closed by an opposite position. `IN` does not currently occur here. See [Common Definition](/common-definition.md#deal-entry). | | `volume` | DECIMAL | Lots. | | `price` | DECIMAL | Execution price. | | `profit` | DECIMAL | Realised profit. Non-zero only on `OUT`, `INOUT` and `OUT_BY`. | | `commission` | DECIMAL | Commission charged on this deal. Usually negative. | | `swap` | DECIMAL | Swap booked with this deal. | | `comment` | STRING | Comment stored on the deal. | | `time` | LONG | Execution time, Unix ms (MT5 resolution is seconds). | **Notes** - **`side` is the stored direction, and its meaning depends on which writer produced the row.** The live deal-stream writer stores the direction of the **closed position** (MT5 exits a long with a SELL deal, so it inverts the deal's action deliberately, for display); the older history sync stores the **deal's** own action. Nothing on the row says which wrote it, so this API surfaces the stored value rather than guessing. **If you need the deal's true direction, take it from the `DEAL` event's `S` field**, which is unambiguous. Making `side` deal-accurate here requires the raw deal action to be persisted alongside the row — a planned broker-side follow-up. - Realised P\&L for a round trip is the sum of `profit + commission + swap` over the deals sharing a `positionId`. Do not read `profit` alone. - Only trading deals are returned. Balance operations, credit grants, commission postings and other non-trading deal types are excluded, so this endpoint does not reconcile to the account balance on its own. - Deals are **append-only** on the trade server, but a broker correction can modify or remove one. Re-reading a window can therefore differ from a previous read; `dealId` remains the key. - This endpoint reads the broker's stored deal history, not the trade server directly, so a deal can take a moment to appear here after the `DEAL` event on the user data stream. The stream is the low-latency path; this endpoint is the durable one. ================================================================================ Page: User Data Streams Overview URL: /user-data-streams.md ================================================================================ # User Data Streams Overview Executions, order changes, position changes and balance changes, pushed as they happen. This is the **only** low-latency way to learn that an order filled. `POST /v1/order` with the default `ACK` tells you the order was accepted; the fill arrives here. ## Connect 1. Create a listenKey: [`POST /v1/listenKey`](/user-data-streams/start-user-data-stream.md). 2. Connect to: ``` wss://trade-stream.yellowboxmarkets.com/ws/ ``` 3. Keep the key alive with [`PUT /v1/listenKey`](/user-data-streams/keepalive-user-data-stream.md) every **30 minutes**. There is no `SUBSCRIBE` step. The connection starts delivering immediately and carries **everything in the key's scope** — exactly the logins [`GET /v1/accounts`](/account.md) lists. A login the REST endpoints would answer `-2022` for (for example an archived account) is not streamed either. > **One connection covers every login the key may trade.** There is no per-login stream and no > per-login listenKey. **Every event carries `L`, the MT5 login it belongs to** — route on it. The connection limits are the same as the market streams: server ping every 3 minutes, dropped after 10 minutes without a pong, 24-hour maximum lifetime, 10 inbound messages per second (inbound messages are otherwise ignored on this stream). See [websocket-market-streams.md § Connection limits](/websocket-market-streams.md#connection-limits). - A listenKey that is unknown, expired or deleted is not refused at the HTTP level: the connection opens, receives one [`listenKeyExpired`](/user-data-streams/listenkeyexpired.md) event and is closed. - While the trading surface is switched off, the upgrade is refused with HTTP 503 and `-1017`. ## Common fields Every event carries these three: | Field | Type | Description | | - | - | - | | `e` | STRING | Event type. | | `E` | LONG | Event time, Unix ms. | | `L` | LONG | MT5 login the event belongs to. | `listenKeyExpired` is the exception — it is a connection-level event and carries no `L`. ## Ordering - **Events for one login are ordered by `E`.** `E` is non-decreasing within a login. - **There is no ordering guarantee across logins.** One login's events may interleave arbitrarily with another's. Do not use `E` as a global sequence. - `DEAL`, `ORDER_UPDATE` and `POSITION_UPDATE` describing the same fill can arrive in any order relative to each other, and can share an `E`. Do not build a state machine that requires `DEAL` before `POSITION_UPDATE`. - **`DEAL` can repeat.** Dedupe on `d`. `ORDER_UPDATE` and `POSITION_UPDATE` can also repeat with identical content; treat them as **state snapshots**, applying the latest by `E`, rather than as deltas. Because they are snapshots, an out-of-order or duplicate `POSITION_UPDATE` is harmless if you compare `E` and keep the newest — which is the recommended way to hold position state. ## Connection close Besides the ordinary cases — you closed it, the 24-hour lifetime elapsed, the server restarted — two closes carry a reason you should act on. The reason is the WebSocket close frame's description, not a JSON event. | Close code | Reason | What happened | What to do | | - | - | - | - | | `1011` | `queue overflow; resnapshot` | Your connection was not reading fast enough and the server's outbound buffer for it filled — or the server's own intake for your logins fell behind under a burst. The alternative was to drop frames silently, and this stream has no sequence number, so you could not have detected that. | **Assume you missed events**, including fills. Reconnect, then **resnapshot** (below) before trusting your state, and read `GET /v1/userTrades` from your last `dealId`. | | `1008` | `scope empty; check /v1/accounts` | Every account the API key could see has left its scope — the accounts were closed, their owner was disabled, or the key's configuration changed. Nothing can be delivered on this connection any more. | Do **not** reconnect in a loop. Read `GET /v1/accounts`; if it is empty, the key has no accounts and this is an operator change, not a fault. | The scope is re-checked while a connection is open, not only when it is accepted: a login removed from the key's scope stops delivering within about 30 seconds, matching when REST stops answering for it. The connection stays open as long as at least one login remains. ## Reconnect **Events that occurred while you were disconnected are not replayed.** There is no sequence number to resume from and no backfill. After any reconnect — a new listenKey, a dropped connection, the 24-hour lifetime, a process restart — **resnapshot before trusting your state**: ``` GET /v1/positions?login=... per login in scope GET /v1/openOrders?login=... per login in scope ``` Then apply stream events on top, keeping the newest `E` per entity. For executions you may have missed, read [`GET /v1/userTrades`](/account/account-trade-list.md) with `fromId` set to the last `dealId` you processed. That is the durable record; the stream is the fast one. Reconnect with exponential backoff and jitter. ## listenKey management Base URL `https://trade-api.yellowboxmarkets.com`. Conventions, signing and error format: [General Info](/general-info.md). These three endpoints manage the **listenKey** that authenticates a user data stream connection. The events themselves are documented in [User Data Streams Overview](/user-data-streams.md). | Endpoint | Security | Weight | | - | - | - | | [`POST /v1/listenKey`](/user-data-streams/start-user-data-stream.md) | `USER_STREAM` | 1 | | [`PUT /v1/listenKey`](/user-data-streams/keepalive-user-data-stream.md) | `USER_STREAM` | 1 | | [`DELETE /v1/listenKey`](/user-data-streams/close-user-data-stream.md) | `USER_STREAM` | 1 | `USER_STREAM` endpoints need the `X-YBX-APIKEY` header but **no signature** — there is nothing to sign, and the key itself is the credential. They take no `login`: one listenKey covers **every** login the key may trade. ================================================================================ Page: Start user data stream (USER_STREAM) URL: /user-data-streams/start-user-data-stream.md ================================================================================ # Start user data stream (USER_STREAM) ## API Description Creates a listenKey, or returns the key's existing one. ## HTTP Request ```http POST /v1/listenKey ``` ## Request Weight 1 ## Request Parameters NONE ## Response Example ```json {"listenKey":"pqia91ma19a5s61cv6a81va65sdf19v8a65a1a5s61cv6a81va65sdf19v8a65a1"} ``` | Field | Type | Description | | - | - | - | | `listenKey` | STRING | Opaque token. Valid for **60 minutes** from creation or from the last keepalive. | Connect with it at: ``` wss://trade-stream.yellowboxmarkets.com/ws/ ``` **Notes** - **One active listenKey per API key.** Calling `POST` again while a key is active returns the **same** listenKey and extends it by 60 minutes — it does not mint a second one and does not invalidate existing connections. To rotate, call `DELETE` then `POST`. - The listenKey is a **bearer credential**. Anyone holding it can read every execution, position and balance change on every account in the key's scope. Treat it like the API secret: never log it, never put it in a URL you share, never expose it to a browser. - It does **not** grant trading. It is read-only. - While the trading surface is switched off this endpoint answers `-1017` (HTTP 503). `PUT` and `DELETE` stay available, so an existing stream can still be kept alive or closed. ================================================================================ Page: Keepalive user data stream (USER_STREAM) URL: /user-data-streams/keepalive-user-data-stream.md ================================================================================ # Keepalive user data stream (USER_STREAM) ## API Description Extends the listenKey by 60 minutes from the moment of the call. ## HTTP Request ```http PUT /v1/listenKey ``` ## Request Weight 1 ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `listenKey` | STRING | NO | The key to extend. Optional — the key's active listenKey is extended when omitted. Sending a listenKey that is not the key's active one — or calling this when the key has no active listenKey at all — is `-1125`. | ## Response Example ```json {} ``` **Send a keepalive every 30 minutes.** Half the lifetime gives you one free retry before expiry. Base the schedule on a timer, not on stream activity — a quiet account produces no events, and an open WebSocket connection does **not** extend the key on its own. If the key does lapse, the stream sends a [`listenKeyExpired`](/user-data-streams/listenkeyexpired.md) event and then closes the connection. Recover by calling `POST /v1/listenKey` again, reconnecting, and **resnapshotting** with `GET /v1/positions` and `GET /v1/openOrders` — events during the gap are not replayed. ================================================================================ Page: Close user data stream (USER_STREAM) URL: /user-data-streams/close-user-data-stream.md ================================================================================ # Close user data stream (USER_STREAM) ## API Description Invalidates the listenKey and closes every WebSocket connection using it. ## HTTP Request ```http DELETE /v1/listenKey ``` ## Request Weight 1 ## Request Parameters | Name | Type | Mandatory | Description | | - | - | - | - | | `listenKey` | STRING | NO | The key to close. Optional — the key's active listenKey is closed when omitted. | ## Response Example ```json {} ``` Call this on a clean shutdown, and immediately if a listenKey is ever exposed. Deleting a listenKey that is already gone is a success, not `-1125` — the endpoint is idempotent. ================================================================================ Page: Event: DEAL URL: /user-data-streams/deal.md ================================================================================ # Event: DEAL ## Event Description An execution. This is the event that tells you an order filled. ## Event Name `DEAL` ## Response Example ```json { "e": "DEAL", "E": 1789012345812, "L": 100123, "d": 55512345, "o": 44412345, "p": 44412345, "s": "XAUUSD", "S": "BUY", "en": "IN", "v": "0.10", "pr": "2331.42", "rp": "0.0000", "n": "-0.7000", "sw": "0.0000", "c": "signal-7", "C": "bot-001", "T": 1789012345000 } ``` | Field | Type | Description | | - | - | - | | `e` | STRING | `"DEAL"` | | `E` | LONG | Event time, Unix ms. | | `L` | LONG | Login. | | `d` | LONG | Deal id. **The natural key — dedupe on it.** | | `o` | LONG | Order id that produced the deal. | | `p` | LONG | Position id the deal opened, added to or closed. | | `s` | STRING | Symbol. | | `S` | ENUM | Deal side, `BUY` or `SELL`. The direction of the **deal**, not of the position: a `SELL` with `en: "OUT"` closes a long. | | `en` | ENUM | Deal entry: `IN`, `OUT`, `INOUT`, `OUT_BY`. See [Common Definition](/common-definition.md#deal-entry). | | `v` | DECIMAL | Volume, lots. | | `pr` | DECIMAL | Execution price. | | `rp` | DECIMAL | Realised profit. Non-zero only on `OUT`, `INOUT`, `OUT_BY`. | | `n` | DECIMAL | Commission on this deal. Usually negative. | | `sw` | DECIMAL | Swap booked with this deal. | | `c` | STRING | The comment **as stored on the MT5 deal**. For a deal this API caused, that is your `comment` with the service's 7-character correlation tag appended — longer than what you sent. See [Comments and deal attribution](/trade.md#comments-and-deal-attribution). | | `C` | STRING | Your `clientOrderId`, resolved from the correlation tag (or from the trade server's answer when it provides one). **This is the field to route on.** Absent for deals this API did not cause — the account holder and the broker can trade the account too, and a stop loss or take profit firing produces an untagged deal. Also absent for a deal caused by **another API key** that shares the login: a `clientOrderId` belongs to the key that sent it and is only ever published on that key's streams. | | `T` | LONG | Deal time, Unix ms (MT5 resolution is seconds, so this is a multiple of 1000). | > **Duplicate `DEAL` events are possible by design.** Gap-fill and reconciliation can republish a > deal that was already delivered. **Dedupe on `d`.** A consumer that books P\&L without deduping > will double-count. Only trading deals are delivered. Balance operations, credit grants and other non-trading deal types do not appear here; their effect shows up on [`ACCOUNT_UPDATE`](/user-data-streams/account_update.md). ================================================================================ Page: Event: ORDER_UPDATE URL: /user-data-streams/order_update.md ================================================================================ # Event: ORDER_UPDATE ## Event Description A pending order was added, changed or removed. ## Event Name `ORDER_UPDATE` ## Response Example ```json { "e": "ORDER_UPDATE", "E": 1789012400500, "L": 100123, "x": "UPDATE", "o": 44412350, "s": "XAUUSD", "ot": "BUY_LIMIT", "X": "NEW", "mst": 1, "q": "0.10", "qr": "0.10", "p": "2323.00", "sp": "0", "sl": "2315.00", "tp": "0", "tif": "GTC", "ts": 1789012345690, "ex": 0, "C": "bot-002" } ``` | Field | Type | Description | | - | - | - | | `e` | STRING | `"ORDER_UPDATE"` | | `E` | LONG | Event time, Unix ms. | | `L` | LONG | Login. | | `x` | ENUM | What happened to the record: `NEW` (order appeared), `UPDATE` (changed), `DELETE` (left the book — filled, cancelled or expired; read `X` for which). | | `o` | LONG | Order id. | | `s` | STRING | Symbol. | | `ot` | ENUM | Order type: `BUY_LIMIT`, `SELL_LIMIT`, `BUY_STOP`, `SELL_STOP`, `BUY_STOP_LIMIT`, `SELL_STOP_LIMIT`. May also be `BUY` or `SELL` for a market order passing through the book — those do not rest, so you will normally see them only in the same breath as their `DELETE` — `CLOSE_BY` for a close-by order placed outside this API, or `UNKNOWN_` for an MT5 type this API does not map. | | `X` | ENUM | Order status: `NEW`, `PARTIALLY_FILLED`, `FILLED`, `CANCELED`, `EXPIRED`, `REJECTED`. | | `mst` | INT | Raw MT5 order state. Provided so an unmapped state is still readable — see [Common Definition](/common-definition.md#order-state-raw-mt5). | | `q` | DECIMAL | Initial volume, lots. | | `qr` | DECIMAL | Remaining volume, lots. | | `p` | DECIMAL | Order price. | | `sp` | DECIMAL | Stop-limit price. **Currently always `"0"`** — the underlying event does not yet carry the stop-limit trigger. Read it back from `GET /v1/openOrders` until it does. | | `sl` | DECIMAL | Stop loss. `"0"` if none. | | `tp` | DECIMAL | Take profit. `"0"` if none. | | `tif` | ENUM | `GTC`, `DAY`, `GTD`, `GTD_DAY`. **Currently inferred, not read:** `GTD` when the order carries an expiration, `GTC` otherwise. `DAY` and `GTD_DAY` are therefore not distinguishable on this event until the underlying event carries the order's time type — read `GET /v1/openOrders` if you must know which. | | `ts` | LONG | Order setup time, Unix ms. | | `ex` | LONG | Expiration, Unix ms. `0` if none. | | `C` | STRING | Your `clientOrderId`, resolved from the order's correlation tag. Absent for orders this API did not place, and for orders another API key placed on a shared login. | `x: "DELETE"` with `X: "FILLED"` means the pending order triggered — expect a `DEAL` and a `POSITION_UPDATE` alongside it. > Two fields on this event are **narrower than they will be**: `tif` is inferred and `sp` is always > `"0"`. Both widen additively — no field changes type or meaning — so a client written against them > today keeps working. ================================================================================ Page: Event: POSITION_UPDATE URL: /user-data-streams/position_update.md ================================================================================ # Event: POSITION_UPDATE ## Event Description A position was opened, changed or closed. ## Event Name `POSITION_UPDATE` ## Response Example ```json { "e": "POSITION_UPDATE", "E": 1789012400000, "L": 100123, "x": "UPDATE", "p": 44412345, "s": "XAUUSD", "S": "BUY", "v": "0.10", "po": "2331.42", "pc": "2331.15", "sl": "2320.00", "tp": "2350.00", "up": "-2.7000", "sw": "0.0000", "ot": 1789012345000, "ut": 1789012400000 } ``` | Field | Type | Description | | - | - | - | | `e` | STRING | `"POSITION_UPDATE"` | | `E` | LONG | Event time, Unix ms. | | `L` | LONG | Login. | | `x` | ENUM | `NEW` (position opened), `UPDATE` (volume, SL/TP, price or profit changed), `CLOSE` (position no longer exists). | | `p` | LONG | Position id. | | `s` | STRING | Symbol. | | `S` | ENUM | `BUY` or `SELL`. | | `v` | DECIMAL | Current open volume, lots. Reduced by a partial close. | | `po` | DECIMAL | Volume-weighted open price. | | `pc` | DECIMAL | Current price at the time of the event. | | `sl` | DECIMAL | Stop loss. `"0"` if none. | | `tp` | DECIMAL | Take profit. `"0"` if none. | | `up` | DECIMAL | Floating (unrealised) profit, in the account's deposit currency. Excludes swap and commission. | | `sw` | DECIMAL | Swap accumulated on the position. | | `ot` | LONG | Position open time, Unix ms. | | `ut` | LONG | Position last-update time, Unix ms. | > **`up` is not tick-fresh.** The trade server recomputes floating profit on a refresh cadence of a > few **seconds**, and `POSITION_UPDATE` fires when that number (or anything else on the position) > changes — not on every tick. This is the trade server's own figure, which is why it reconciles > with the account statement. > > If you need per-tick mark-to-market, compute it from the [`@tick`](/websocket-market-streams/tick-stream.md) > stream and `po`. Use `up` for anything that must agree with the broker. `x: "CLOSE"` means the position is gone. The matching `DEAL` with `en: "OUT"` carries the realised profit; `up` on the close event is not a realised figure. ================================================================================ Page: Event: ACCOUNT_UPDATE URL: /user-data-streams/account_update.md ================================================================================ # Event: ACCOUNT_UPDATE ## Event Description The account's realised balance or credit changed — a fill settling, a deposit, a withdrawal, a credit grant, a commission or swap posting. ## Event Name `ACCOUNT_UPDATE` ## Response Example ```json { "e": "ACCOUNT_UPDATE", "E": 1789012600500, "L": 100123, "x": "UPDATE", "g": "real\\BOT\\ECN", "b": "10001.8000", "cr": "0.0000" } ``` | Field | Type | Description | | - | - | - | | `e` | STRING | `"ACCOUNT_UPDATE"` | | `E` | LONG | Event time, Unix ms. | | `L` | LONG | Login. | | `x` | ENUM | `NEW`, `UPDATE` or `DELETE` on the underlying account record. `UPDATE` in practice. | | `g` | STRING | MT5 group. Tells you whether the account is a cent account. | | `b` | DECIMAL | Realised balance, in the account's deposit currency. | | `cr` | DECIMAL | Non-withdrawable credit. | > **Equity, margin, free margin and margin level are NOT on this event.** They are not part of the > underlying record this event is built from, and publishing a zero would be worse than publishing > nothing. Read them from [`GET /v1/account`](/account/account-information.md), or derive > equity as `b + cr + Σ(up + sw)` over the positions you are tracking from `POSITION_UPDATE`. > > If your risk logic depends on margin level, poll `GET /v1/account` at a rate you can afford and > treat the derived figure as an estimate between polls. ================================================================================ Page: Event: listenKeyExpired URL: /user-data-streams/listenkeyexpired.md ================================================================================ # Event: listenKeyExpired ## Event Description The listenKey lapsed. The connection closes immediately after this frame. ## Event Name `listenKeyExpired` ## Response Example ```json {"e":"listenKeyExpired","E":1789013945678,"listenKey":"pqia91ma19a5s61cv6a81va65sdf19v8a65a1a5s61cv6a81va65sdf19v8a65a1"} ``` | Field | Type | Description | | - | - | - | | `e` | STRING | `"listenKeyExpired"` | | `E` | LONG | Event time, Unix ms. | | `listenKey` | STRING | The key that expired. | The same frame, followed by the close (code `1000`, reason `listenKey expired`), is sent when the key lapses, when you call [`DELETE /v1/listenKey`](/user-data-streams/close-user-data-stream.md), and when you connect with a key that does not exist. Expiry is checked about every 30 seconds, so the frame can arrive up to that long after the 60-minute lifetime ends. Recover: `POST /v1/listenKey`, reconnect, then **resnapshot** (below). Seeing this event means your keepalive schedule is wrong — it should fire every 30 minutes on a timer. ================================================================================ Page: WebSocket Market Streams Overview URL: /websocket-market-streams.md ================================================================================ # WebSocket Market Streams Overview Live prices, candles and statistics. Market streams are **public** — no API key, no signature, no `login`. Account events are on a separate, authenticated connection: [User Data Streams Overview](/user-data-streams.md). ## Endpoints | Form | URL | | - | - | | Raw | `wss://trade-stream.yellowboxmarkets.com/ws` | | Raw, pre-subscribed | `wss://trade-stream.yellowboxmarkets.com/ws/XAUUSD@tick` | | Combined | `wss://trade-stream.yellowboxmarkets.com/stream?streams=XAUUSD@tick/BTCUSD@kline_1m` | On the **raw** endpoint, payloads arrive exactly as documented below. On the **combined** endpoint, every payload is wrapped: ```json {"stream":"XAUUSD@tick","data":{"e":"tick","E":1789012345678,"s":"XAUUSD","b":"2331.15","a":"2331.42","l":"2331.15","v":8,"bd":1,"ad":-1,"T":1789012345678}} ``` Streams in the `streams=` query parameter are separated by `/`. URL-encode the value if a symbol name contains a character that needs it. > **Stream names are case-sensitive and are NOT lowercased.** Unlike Binance, the symbol part of a > stream name is the exact MT5 symbol name. `XAUUSD@tick` is correct; `xauusd@tick` does not exist > and subscribing to it returns an error frame. The stream-type suffix is spelled exactly as > documented — `@tick`, `@bookTicker`, `@kline_1m`, `@ticker`, `@depth` — and is case-sensitive too. ## Subscribe protocol Send JSON text frames. ### SUBSCRIBE ```json {"method":"SUBSCRIBE","params":["XAUUSD@tick","BTCUSD@kline_1m"],"id":1} ``` ```json {"result":null,"id":1} ``` ### UNSUBSCRIBE ```json {"method":"UNSUBSCRIBE","params":["XAUUSD@tick"],"id":2} ``` ```json {"result":null,"id":2} ``` ### LIST_SUBSCRIPTIONS ```json {"method":"LIST_SUBSCRIPTIONS","id":3} ``` ```json {"result":["BTCUSD@kline_1m"],"id":3} ``` ### Request fields | Field | Type | Mandatory | Description | | - | - | - | - | | `method` | STRING | YES | `SUBSCRIBE`, `UNSUBSCRIBE` or `LIST_SUBSCRIPTIONS`. | | `params` | ARRAY of STRING | conditional | Stream names. Required for `SUBSCRIBE` / `UNSUBSCRIBE`. | | `id` | UNSIGNED INT | YES | Chosen by you. Echoed on the response so you can correlate. Reuse is allowed but makes correlation ambiguous. | ### Error frames ```json {"code":-1121,"msg":"Invalid symbol.","id":1} ``` ```json {"code":-1120,"msg":"Invalid interval.","id":4} ``` ```json {"code":-1101,"msg":"Too many parameters sent for this endpoint.","id":5} ``` | Code | When | | - | - | | `-1121` | A stream name whose symbol does not exist (exact case), or that is malformed or of an unknown stream type. | | `-1120` | A `kline_` stream with an unsupported interval. | | `-1101` | The subscription would exceed 200 streams on the connection. | | `-1102` | `id`, `method` or (for `SUBSCRIBE` / `UNSUBSCRIBE`) `params` missing or of the wrong type. When `id` itself is unusable the frame carries `"id":null`. | | `-1020` | An unknown `method`. | | `-1130` | The frame is not a JSON object. `"id":null`. | An error frame never closes the connection. `UNSUBSCRIBE` of a stream you are not subscribed to is a no-op and answers success. A `SUBSCRIBE` with several `params` is **all-or-nothing**: if one name is invalid the whole frame is rejected and nothing is subscribed. Subscribe in small batches so one bad symbol does not cost you the rest. Codes are the same ones documented in [Error Codes](/error-codes.md). ## Connection limits | Limit | Value | | - | - | | Ping frame from the server | every **3 minutes** | | Disconnect if no pong | after **10 minutes** without one | | Maximum connection lifetime | **24 hours** | | Streams per connection | **200** | | Inbound messages per second | **10** | | Concurrent connections per client IP | **20** | - Reply to every WebSocket **ping** frame with a **pong**. Most client libraries do this automatically; verify yours does. A missed pong window costs you the connection. - You may send unsolicited pong frames as a heartbeat; they are ignored and do not count toward the message rate. - A connection is **closed at 24 hours** regardless of activity. Reconnect before then. Overlap the connections — open the new one, subscribe, then drop the old one — so you do not miss ticks. - Exceeding 10 inbound messages per second closes the connection immediately. Batch stream names into one `SUBSCRIBE` rather than sending one frame per stream. - Exceeding 200 streams returns `-1101` and subscribes nothing. - A connection beyond the per-IP limit — or beyond the service's overall capacity — is refused **before** the WebSocket upgrade with HTTP 503 and `-1008`. Put your streams on fewer connections (up to 200 each) rather than opening one per symbol. - A plain HTTP request (no WebSocket upgrade) to a stream URL is answered HTTP 400 with `-1020`. - An inbound message larger than 64 KB closes the connection. - On the **pre-subscribed** and **combined** URLs, an invalid stream name in the URL is answered with one error frame (as above, `"id":null`) and the connection is then closed — nothing is subscribed. The server closes a connection with these WebSocket close codes: | Close code | Reason | Cause | | - | - | - | | `1000` | `connection lifetime reached` | The 24-hour lifetime elapsed (or the service is restarting). | | `1000` | `invalid stream name` | An invalid name in a pre-subscribed or combined URL. | | `1008` | `message rate exceeded` | More than 10 inbound messages in one second. | | `1009` | `frame too large` | An inbound message over 64 KB. | There are **no rate-limit weight headers** on WebSocket. Market streams do not consume REST request weight, which is exactly why you should use them instead of polling. ## Reconnect guidance Market streams carry no state and are not replayed. On reconnect: 1. Re-subscribe to everything you had. `LIST_SUBSCRIPTIONS` on the **new** connection is the way to verify, not to discover. 2. Refresh anything you were accumulating from the stream: candles from `GET /v1/klines`, and any last-price cache from `GET /v1/ticker/price`. 3. Assume you missed ticks during the gap. Never derive position state from the tick stream — read it from [`GET /v1/positions`](/trade/open-positions.md). Reconnect with backoff and jitter. A fleet of bots reconnecting in lockstep after a restart is self-inflicted load. ================================================================================ Page: Tick stream URL: /websocket-market-streams/tick-stream.md ================================================================================ # Tick stream ## Stream Description The primary price feed: one message per quote update from the trade server. ## Stream Name `@tick` ## Update Speed on every quote change. Bursty — an active symbol can produce many per second, a quiet one none for minutes. ## Response Example ```json {"e":"tick","E":1789012345678,"s":"XAUUSD","b":"2331.15","a":"2331.42","l":"2331.15","v":8,"bd":1,"ad":-1,"T":1789012345678} ``` | Field | Type | Description | | - | - | - | | `e` | STRING | `"tick"` | | `E` | LONG | Event time, Unix ms. | | `s` | STRING | Symbol, exact case. | | `b` | DECIMAL | Bid. | | `a` | DECIMAL | Ask. | | `l` | DECIMAL | Last traded price. `"0"` on symbols where the trade server reports no last price. | | `v` | LONG | Tick volume reported with the quote. | | `bd` | INT | Bid direction versus the previous tick: `1` up, `-1` down, `0` unchanged. | | `ad` | INT | Ask direction, same encoding. | | `T` | LONG | Tick time, Unix ms. | > `T` and `E` are currently produced from the same clock and will be equal. `T` exists because the > trade server's own tick timestamp is the value that belongs there, and it will carry that value > when the feed exposes it. Treat `T` as the price time and `E` as the delivery time; do not assume > they stay identical. Subscribing to a symbol that nobody is watching starts the feed for it. The first tick can take a moment to arrive. ================================================================================ Page: Book ticker stream URL: /websocket-market-streams/book-ticker-stream.md ================================================================================ # Book ticker stream ## Stream Description Best bid and ask, Binance-shaped. **This is the same underlying quote as `@tick`** — MT5 publishes one bid and one ask with no size attached, so `B` and `A` are always `"0"`. The stream exists so a Binance-shaped client can subscribe to the name it expects. If you want the real book, use [`@depth`](/websocket-market-streams/symbol-depth-stream.md). If you want direction and tick volume, use [`@tick`](/websocket-market-streams/tick-stream.md) — it is strictly more informative than this stream. ## Stream Name `@bookTicker` ## Response Example ```json {"e":"bookTicker","E":1789012345678,"s":"XAUUSD","b":"2331.15","B":"0","a":"2331.42","A":"0","T":1789012345678} ``` | Field | Type | Description | | - | - | - | | `e` | STRING | `"bookTicker"` | | `E` | LONG | Event time, Unix ms. | | `s` | STRING | Symbol. | | `b` | DECIMAL | Best bid price. | | `B` | DECIMAL | Best bid quantity. Always `"0"`. | | `a` | DECIMAL | Best ask price. | | `A` | DECIMAL | Best ask quantity. Always `"0"`. | | `T` | LONG | Quote time, Unix ms. | There is no update id (`u`) — there is no book to sequence. ================================================================================ Page: Kline streams URL: /websocket-market-streams/kline-streams.md ================================================================================ # Kline streams ## Stream Description Live candles are built **from the tick stream as it arrives**. Historical candles come from [`GET /v1/klines`](/market-data/klines-candlestick-data.md), which is built from the trade server's own bars. ## Stream Name `@kline_` **Intervals:** `1m`, `5m`, `15m`, `30m`, `1h`, `2h`, `4h`, `1d` ## Update Speed on every tick that changes the candle, plus a final message when the candle closes. ## Response Example ```json {"e":"kline","E":1789012345678,"s":"XAUUSD","k":{"t":1789012320000,"T":1789012379999,"s":"XAUUSD","i":"1m","o":"2331.02","c":"2331.15","h":"2331.40","l":"2330.95","v":38,"n":38,"x":false}} ``` | Field | Type | Description | | - | - | - | | `e` | STRING | `"kline"` | | `E` | LONG | Event time, Unix ms. | | `s` | STRING | Symbol. | | `k` | OBJECT | The candle. | | `k.t` | LONG | Candle open time, Unix ms. | | `k.T` | LONG | Candle close time (last ms of the interval), Unix ms. | | `k.s` | STRING | Symbol. | | `k.i` | STRING | Interval. | | `k.o` | DECIMAL | Open. | | `k.c` | DECIMAL | Close — the latest price while `x` is `false`. | | `k.h` | DECIMAL | High. | | `k.l` | DECIMAL | Low. | | `k.v` | LONG | Tick volume: ticks counted in the candle so far. | | `k.n` | LONG | Number of ticks. Identical to `k.v` — MT5's volume for most instruments **is** the tick count. Both are present for Binance shape compatibility. | | `k.x` | BOOLEAN | `true` on the final message for a closed candle. | **Notes** - Prices are **bid** prices, matching MT5 chart convention. - Real traded volume is not on this stream. `GET /v1/klines` carries it as `realVolume`, and it is `0` on most forex and CFD symbols. - Candle buckets are aligned to the **Unix epoch in UTC**. `1d` candles open at 00:00 UTC, which is not the broker's trading day — the same caveat as the REST endpoint. - A candle whose interval sees no ticks is not emitted. Gaps are normal over weekends and closures. - Candles only exist from the moment the service starts tracking the symbol. Backfill from `GET /v1/klines` and then switch to the stream; expect the first live candle after you subscribe to be partial. ================================================================================ Page: Individual symbol ticker stream URL: /websocket-market-streams/individual-symbol-ticker-stream.md ================================================================================ # Individual symbol ticker stream ## Stream Description Daily statistics, the same data as [`GET /v1/ticker/24hr`](/market-data/24hr-ticker-statistics.md), **throttled to at most one message per second** per symbol. The same caveat applies: these are the trade server's **trading-day** statistics, not a rolling 24-hour window, and the activity counters are broker-wide. ## Stream Name `@ticker` ## Response Example ```json {"e":"24hrTicker","E":1789012345678,"s":"XAUUSD","o":"2325.40","h":"2338.90","l":"2321.05","c":"2331.15","b":"2331.15","a":"2331.42","p":"5.75","P":"0.247","vt":"0.731","n":18422,"v":"9214.50","bo":9611,"so":8811} ``` | Field | Type | Description | | - | - | - | | `e` | STRING | `"24hrTicker"` | | `E` | LONG | Event time, Unix ms. | | `s` | STRING | Symbol. | | `o` | DECIMAL | Day open price. | | `h` | DECIMAL | Day high (bid). | | `l` | DECIMAL | Day low (bid). | | `c` | DECIMAL | Last price. | | `b` | DECIMAL | Current best bid. | | `a` | DECIMAL | Current best ask. | | `p` | DECIMAL | Price change, `c - o`. | | `P` | DECIMAL | Price change percent, derived from `o` and `c` on this stream. (`GET /v1/ticker/24hr` may instead report the trade server's own statistic — expect the two to agree to rounding, not bit-for-bit.) | | `vt` | DECIMAL | Volatility percent, as the trade server computes it. | | `n` | LONG | Deals on the symbol today, broker-wide. | | `v` | DECIMAL | Traded volume today as reported by the trade server, in its volume units (lots for most symbols), broker-wide. | | `bo` | LONG | Buy orders today, broker-wide. | | `so` | LONG | Sell orders today, broker-wide. | ================================================================================ Page: Symbol depth stream URL: /websocket-market-streams/symbol-depth-stream.md ================================================================================ # Symbol depth stream ## Stream Description Level-2 market depth from the trade server's order book. > **Optional — off by default.** Market depth is only published for symbols the broker has enabled > it for. **Subscribing always SUCCEEDS** — the subscription is accepted and then simply delivers > nothing, because whether depth flows is a broker-side setting this API cannot see. Silence on > `@depth` therefore means "not enabled", not "not subscribed": confirm with the broker rather than > waiting on an error that will not come. ## Stream Name `@depth` ## Response Example ```json {"e":"depth","E":1789012345678,"s":"XAUUSD","T":1789012345670,"b":[["2331.15","2.50"],["2331.10","4.00"]],"a":[["2331.42","1.00"],["2331.48","3.25"]]} ``` | Field | Type | Description | | - | - | - | | `e` | STRING | `"depth"` | | `E` | LONG | Event time, Unix ms. | | `s` | STRING | Symbol. | | `T` | LONG | Book timestamp from the trade server, Unix ms. | | `b` | ARRAY | Bid levels, each `[price, volumeLots]`. | | `a` | ARRAY | Ask levels, each `[price, volumeLots]`. | **Notes** - Each message is a **complete snapshot** of the book, not a diff. There are no `U`/`u` update ids and there is nothing to reconstruct — replace your local book wholesale on every message. - Level ordering is the trade server's. **Sort by price yourself** if you need ordered depth. - Volumes are lots. - Depth is informational. Orders are still filled by the broker at bid/ask under the symbol's execution mode; a visible level is not a guaranteed fill.