TICKERALL!
Developer reference

TickerAll API Docs

Everything you need to connect a broker account and trade it from your own code — REST endpoints, the realtime WebSocket, authentication, and copy-paste examples in curl, TypeScript, and Python.

Overview

TickerAll is a hosted API for connecting to and automating your own MT4/MT5 broker accounts. You write your strategy in any language; we hold a fast, persistent connection straight to your broker and keep it live for you, exposed over a clean REST + WebSocket API — so there's no MT4/MT5 terminal in the path, no EA bridge to wire up, and no reconnect churn when your strategy needs it.

The API has two planes:

REST control + data plane

https://api.tickerall.com

Connect accounts, read balance and positions, list symbols, and place / close / modify orders. Standard JSON over HTTPS.

Realtime data plane

wss://api.tickerall.com

A single long-lived WebSocket streams live ticks, position updates, and account updates as they happen.

One API key can hold many broker accounts at once — each scoped by its accountId. Symbol names are pass-through: you see the broker’s native names (e.g. EURUSD, BTCUSD), with no remapping. Prefer a typed client over raw HTTP? Grab the official SDKs linked at the top — TypeScript and Python, both wrapping this same REST + WebSocket API.

Authentication

Every request authenticates with a TickerAll API key sent as a bearer token. Sign up, open API keys in your dashboard, and create a key — it looks like cf_api_…. Treat it like a password; it carries the access of your whole account.

HTTP header
Authorization: Bearer cf_api_xxxxxxxxxxxxxxxxxxxx

Send this header on every REST call. For the WebSocket, send the same header on the upgrade request, or pass ?token=<key> in the URL where headers are awkward (e.g. browser clients).

A missing or invalid key returns 401 UNAUTHORIZED. Keys are validated on our side; revoking a key from the dashboard takes effect within a few minutes.

Read-only keys. When creating a key you can mark it Read-only — a data-only credential that reads candles, symbols, accounts, and history but is rejected with 403 FORBIDDEN on any trade or account mutation. Ideal for backtesting, analytics, or any integration that should never place orders. A full (default) key can trade.

Quickstart

From zero to a live order in four calls — then, on Pro, fan the same actions out across every account at once (step 5). Each step builds on the last. Prefer to click through it first? The dashboard's Test panel runs every one of these calls from your browser — no code.

01

Connect a broker account

POST your broker credentials to /v1/sessions. We authenticate against the broker and return an accountId. By default your password is held in memory only while your connection is live, never saved to disk. The only exception is the opt-in remembered credentials setting (Pro and Enterprise, for customers who can't keep credentials on their own side): switch it on and we store the password encrypted, solely to restore the connection after a restart; switch it off and it is deleted immediately.

curl -X POST https://api.tickerall.com/v1/sessions \
  -H "Authorization: Bearer cf_api_xxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "broker": "mt5",
    "server": "Exness-MT5Trial14",
    "account": 12345678,
    "password": "your-broker-password",
    "terminalType": "MOBILE"
  }'
# terminalType is optional — "MOBILE" (default), "WEB", or "CLIENT" (desktop terminal). WEB/CLIENT require Pro or Enterprise.

# => { "accountId": "acc_8Kd3...", "isDemo": true, "status": "connected", ... }
02

Read account state

Use the accountId to read balance, equity, and open positions.

curl https://api.tickerall.com/v1/accounts/acc_8Kd3... \
  -H "Authorization: Bearer cf_api_xxxx"

# => { "status": "online", "account": { "balance": 9871.42, ... }, "positions": [...] }
03

Place and close an order

State-changing calls require an Idempotency-Key header — a unique string per logical action, so a retried request never double-fires.

# Open a 0.10-lot BUY market order (note the unique Idempotency-Key)
curl -X POST https://api.tickerall.com/v1/accounts/acc_8Kd3.../orders \
  -H "Authorization: Bearer cf_api_xxxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "type": "market", "symbol": "BTCUSD", "side": "BUY",
        "volume": 0.10, "stopLoss": 71000, "takeProfit": 84000 }'
# => { "ticket": 4072808150, "status": "open", ... }

# Close it (ticket from the response above)
curl -X DELETE https://api.tickerall.com/v1/accounts/acc_8Kd3.../positions/4072808150 \
  -H "Authorization: Bearer cf_api_xxxx" \
  -H "Idempotency-Key: $(uuidgen)"
# => { "ticket": 4072808150, "closed": true, ... }
04

Stream live ticks

Open the WebSocket and subscribe to the symbols you care about.

# curl can't speak WebSocket; use websocat (or any WS client).
# Install websocat: brew install websocat | cargo install websocat | apt install websocat
websocat "wss://api.tickerall.com/v1/stream?token=cf_api_xxxx"

# then paste a subscribe frame:
{"type":"subscribe","channels":[{"kind":"ticks","accountId":"acc_8Kd3...","symbols":["BTCUSD"]}]}

# server streams:
# {"type":"tick","symbol":"BTCUSD","bid":77512.73,"ask":77514.10,"timestamp":"..."}
05

Execute across accounts (Pro)

Run the same action over many accounts in one call, then read all their state at once. Every bulk call reports each account's outcome separately, so a partial success is clear. Requires a Pro or Enterprise plan.

# Place the same 0.10-lot BUY across TWO accounts in one call (Pro/Enterprise)
curl -X POST https://api.tickerall.com/v1/bulk/orders \
  -H "Authorization: Bearer cf_api_xxxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "orders": [
        { "accountId": "acc_8Kd3...", "symbol": "BTCUSD", "side": "BUY", "volume": 0.10 },
        { "accountId": "acc_9Lm4...", "symbol": "BTCUSD", "side": "BUY", "volume": 0.10 }
      ] }'
# => { "results": [ { "accountId": "acc_8Kd3...", "status": "filled", ... }, ... ],
#      "summary": { "total": 2, "filled": 2, "failed": 0 } }

# Read live state for many accounts in one call
curl "https://api.tickerall.com/v1/bulk/accounts?ids=acc_8Kd3...,acc_9Lm4..." \
  -H "Authorization: Bearer cf_api_xxxx"
# => { "accounts": [ { "id": "acc_8Kd3...", "status": "online", ... }, ... ],
#      "summary": { "total": 2, "online": 2, "offline": 0 } }

Postman & Insomnia

Prefer a GUI client? Import a ready-made collection — every REST endpoint below, with the Authorization bearer token wired up, an auto-generated Idempotency-Key on every write, example request bodies, and an example response on each call. Set two variables and start sending. Download the file, or use the Copy to clipboard option and paste it straight into the app.

PostmanInsomnia

Use the Download ▾ menu to switch between saving the .json file and Copy to clipboard — then paste it straight into Postman or Insomnia, no file needed.

Postman
  1. Import → drop in the file, or Raw text → paste the copied JSON.
  2. Open the collection’s Variables and set apiKey to your cf_api_… key.
  3. Run Open a broker session, then set accountId (and ticket once you have one).
Insomnia
  1. Import → From File, or From Clipboard after copying.
  2. Open Manage Environments and set apiKey, then accountId.
  3. The realtime feed is included as a ready WebSocket request.

baseUrl and wsBaseUrl come pre-filled. The WebSocket connect URL and subscribe frame are bundled in too — as a runnable request in Insomnia, and documented in the WebSocket (realtime) folder in Postman.

Conventions

Base URLs

REST: https://api.tickerall.com · WebSocket: wss://api.tickerall.com/v1/stream

Content type

Request and response bodies are JSON. Send Content-Type: application/json on calls with a body.

Idempotency-Key (required on writes)

Every state-changing call (POST orders, DELETE / PATCH positions) requires an Idempotency-Key: <unique-string> header. We store (key → response) for 24 hours; resending the same key with the same request replays the original response without re-executing. A key identifies one request: re-using it with a different body or endpoint is a 409 IDEMPOTENCY_KEY_REUSED — nothing is replayed or executed. Omitting the header is a VALIDATION_ERROR. Use a fresh UUID per logical action. The guarantee is the 24-hour window: inside it, a key can execute at most once, however many times you retry it. After 24 hours the record is gone, so re-sending the same key is a brand-new request and will execute again — keep retries of a write inside that window, and treat anything older as a fresh action.

Rate limits

Limits are per API key, counted in a one-minute window that starts at your first call: 300 order writes (placing orders, cancelling or modifying pending orders, and the bulk order calls — a bulk call counts once, however many accounts it covers), 600 candle and history calls, 120 POST /v1/sessions, and 2,000 for everything else, including closing or modifying a position. Over a limit you get 429 rate_limited; wait for the minute to reset. For prices, positions and orders, use the WebSocket instead of polling.

Timestamps & numbers

Timestamps are ISO-8601 UTC strings. Tickets are integers. Prices and volumes are JSON numbers (lots for volume, e.g. 0.10).

Connection warmth

We keep a broker connection “hot” for a window after you start a session. If it cools, calls return 409 BROKER_ACCOUNT_NOT_HOT — just POST /v1/sessions again to re-warm it.

MT4 vs MT5 coverage

Both platforms connect, stream live prices, report open positions and trade. Three things are MT5-only today: closed-trade history (/history, /deals-history, /orders-history, /balance-operations answer 501 MT4_HISTORY_UNAVAILABLE on MT4), symbol specifications (/symbol-specs returns an empty list; /symbols/:symbol/spec and /symbols/:symbol/quote return 404), and deep candle history (an MT4 response reports coverage: "partial" with complete: false — never a false-empty set). You will always get one of those honest answers rather than an empty result that looks like no data. If your integration needs history or contract specs, connect the same broker account as MT5.

Sessions

A session is a warm, authenticated connection to one broker account. Start one to get an accountId; delete it to disconnect.

POST/v1/sessions

Connect a broker account. We authenticate and warm a live connection, then hand back an accountId — the handle for THIS broker connection. One API key can hold several broker accounts, so the id lives in this response (and in GET /v1/accounts), NOT in the key. Use it in every later /v1/accounts/:id call. By default your password is held in memory only while the connection is live, never saved to disk; the one exception is the opt-in remembered-credentials setting (Pro and Enterprise, PATCH /v1/remember-password), which stores it encrypted solely to restore the connection after a restart.

Request body (JSON)
FieldTypeDescription
brokerreq"mt4" | "mt5"Which platform your broker server runs.
serverreqstringBroker server name, e.g. "Exness-MT5Trial14".
accountreqnumber | stringYour numeric broker login — the account number your broker assigns (e.g. 12345678). NOT your TickerAll email.
passwordreqstringBroker (investor or master) password. Used once to authenticate; never persisted unless you have opted in to remembered credentials (Pro and Enterprise), in which case it may be omitted on reconnect.
terminalTypeoptional"MOBILE" | "WEB"Which client the connection presents AS — MOBILE (default) or WEB. Both expose the full surface (account, quotes, positions, history). Omit for MOBILE. Choosing WEB (or CLIENT) requires a Pro or Enterprise plan.
webTerminalUrloptionalstringThe broker’s web-terminal URL, e.g. https://mt5.yourbroker.com. REQUIRED when terminalType is "WEB" — web terminals are per-broker-domain, so the URL must be supplied. Ignored for MOBILE.
webEndpointoptionalstringAdvanced, optional: an explicit WebSocket endpoint override (e.g. wss://host/path) for the rare broker whose WS host/path differs from the webTerminalUrl derivation. Ignored for MOBILE.
200 OK
{
  "accountId": "acc_8Kd3...",
  "isDemo": true,
  "status": "connected",
  "expiresAt": "2026-05-22T18:42:10.000Z"
}
  • ▸On Free, demo accounts plus one live account in the Terminal; a live account via the API, SDKs or MCP returns FREE_TIER_LIVE_REJECTED.
  • ▸The connection stays warm for a while (see expiresAt). If it cools, calls return BROKER_ACCOUNT_NOT_HOT — just POST /v1/sessions again to reconnect.
DELETE/v1/sessions/:accountId

Disconnect a broker account and release its connection. Returns no body.

Path parameters
FieldTypeDescription
accountIdreqstringThe accountId returned by POST /v1/sessions.
204 No Content
(empty body)

Accounts

List your connected accounts, or fetch one account’s live financials and open positions.

GET/v1/accounts

List every broker account attached to your API key, with connection state.

200 OK
[
  {
    "id": "acc_8Kd3...",
    "broker": "mt5",
    "server": "Exness-MT5Trial14",
    "accountNumber": "****5678",
    "isDemo": true,
    "group": "demo\\Standard",
    "status": "CONNECTED",
    "hot": true,
    "marginMode": "netting",
    "alwaysHot": false,
    "lastHotAt": "2026-05-22T18:30:01.000Z",
    "createdAt": "2026-05-20T09:11:55.000Z"
  }
]
  • ▸accountNumber is masked to the last 4 digits. hot=false means the connection cooled — POST /v1/sessions to re-warm it.
  • ▸marginMode is the broker’s margin model for the account — "netting" (one aggregate position per symbol), "hedging" (independent positions per symbol), or "exchange". It is read from the live warm connection, so it is null when the account is cold/offline or on MT4.
  • ▸group is the broker’s own account group (e.g. demo\Standard, real\Raw-USD), the best account-type signal available. It is cached per account from the first connection or trade the broker reports it on; null only for an account that has never once been connected.
  • ▸Each row carries group and serverTimeOffsetSeconds as last measured on the account (cached across sessions and restarts); null means never seen yet.
  • ▸Only accounts that connected at least once are listed: a failed connection attempt (unknown server, wrong password, unreachable broker) leaves no entry and does not count toward your plan’s cap.
GET/v1/accounts/:id

Live account info — balance, equity, margin, leverage — plus the current open positions.

Path parameters
FieldTypeDescription
idreqstringaccountId.
200 OK (status: "online")
{
  "id": "acc_8Kd3...",
  "broker": "mt5",
  "server": "Exness-MT5Trial14",
  "accountNumber": "****5678",
  "isDemo": true,
  "status": "online",
  "account": {
    "name": "Demo Account 12345678",
    "accountType": "demo",
    "group": "demo\\Standard",
    "leverage": 500,
    "balance": 9871.42,
    "currency": "USD",
    "equity": 9863.10,
    "margin": 142.50,
    "freeMargin": 9720.60,
    "marginLevel": 6921.5,
    "credit": null,
    "isCent": false,
    "cent": null,
    "serverTimeOffsetSeconds": 0
  },
  "positions": [
    {
      "ticket": 4072808150,
      "symbol": "BTCUSD",
      "side": "BUY",
      "volume": 0.10,
      "entryPrice": 77512.73,
      "stopLoss": 71000,
      "takeProfit": 84000,
      "currentPrice": 77640.10,
      "profit": 12.74,
      "swap": 0,
      "commission": 0,
      "comment": "my-strategy",
      "magic": 0,
      "openTime": "2026-05-22T17:55:03.000Z",
      "brokerOpenTime": "2026-05-22T17:55:03+00:00"
    }
  ]
}
  • ▸If the connection has cooled, you get status:"offline" with a hint instead of live data — POST /v1/sessions to reconnect.
  • ▸Money fields (equity, margin, freeMargin, marginLevel) may be null on an MT4 account that has not yet pushed a balance frame — null is honest "not available yet", never a misleading 0.
  • ▸credit: broker-granted credit / deposit bonus in the account currency, already included in equity and freeMargin. A number when the broker reports it (0 means no credit); null only when the broker has not reported a credit figure for the account.
  • ▸isCent / cent: whether the account is denominated in a cent unit. The broker reports a cent account’s deposit currency as a cent code — currency "USC" means US cents — and every money field (balance, equity, margin, profit, deal P/L) is in that unit. isCent true comes with cent: { baseCurrency: "USD", unitsPerBase: 100 } (divide by unitsPerBase for dollars); a whole-currency account reads isCent false, cent null; isCent null means the deposit currency is not known yet (no snapshot).
  • ▸Each open position carries brokerOpenTime — the same instant as openTime in the broker’s server-local time (RFC 3339 with offset) — on this object and on GET /v1/accounts/:id/positions, whose envelope also carries serverTimeOffsetSeconds. The offset is derived from the account’s open positions as well as its closed deals, so it is known from the first session on any account holding a position.
  • ▸serverTimeOffsetSeconds is the broker’s server-local offset from UTC (e.g. 10800 for GMT+3), measured from the broker’s own records. null means it could not be derived yet (no deal or open position on the account, or MT4); 0 means a UTC server.
  • ▸group is the broker’s own account group (e.g. demo\Raw-USD, real\Standard) — the best account-type signal the broker exposes. The broker sends it on an account’s first connection and on trade events; TickerAll caches it per account from that point, so it is present on the account object and the WebSocket snapshot on every later session and survives reconnects and maintenance. The only time it is null is an account that has never once been connected — never a made-up value.
GET/v1/accounts/:id/positions

The account’s current open positions — the dedicated positions feed. Each row carries entry price, volume, live P&L and SL/TP; it is the same array GET /v1/accounts/:id embeds, with a brokerOpenTime twin per row and serverTimeOffsetSeconds on the envelope.

Path parameters
FieldTypeDescription
idreqstringaccountId.
200 OK
{
  "accountId": "acc_8Kd3...",
  "positions": [
    {
      "ticket": 4072808150,
      "symbol": "BTCUSD",
      "side": "BUY",
      "volume": 0.10,
      "entryPrice": 77512.73,
      "stopLoss": 71000,
      "takeProfit": 84000,
      "currentPrice": 77640.10,
      "profit": 12.74,
      "swap": 0,
      "commission": 0,
      "comment": "my-strategy",
      "magic": 0,
      "openTime": "2026-05-22T17:55:03.000Z",
      "brokerOpenTime": "2026-05-22T17:55:03+00:00"
    }
  ],
  "serverTimeOffsetSeconds": 0
}
  • ▸An empty positions array is valid data — it means no open positions, never an error.
  • ▸entryPrice is the position’s actual fill price; currentPrice and profit are live and move with the market. profit is in the account’s deposit currency.
  • ▸magic is the EA (Expert Advisor) magic number the trade was opened with — the integer a bot stamps its orders with; 0 means a manual or non-EA trade (0 is a real value, not "missing"). The same field is on pending orders.
  • ▸openTime is UTC; brokerOpenTime is the same instant in the broker’s server-local time (RFC 3339 with offset). serverTimeOffsetSeconds on the envelope is the broker’s offset from UTC (e.g. 10800 for GMT+3); null means it could not be derived yet, 0 means a UTC server.
  • ▸The same positions are also on GET /v1/accounts/:id alongside the money fields — use this lighter call when you only need positions, and subscribe to the positions WebSocket channel for live updates.
DELETE/v1/accounts/:id

Remove a broker account from your roster. We disconnect its live connection and drop it from your account list and from billing — this does NOT touch the broker account itself or any open positions. Reversible: reconnect the same login with POST /v1/sessions to re-add it.

Path parameters
FieldTypeDescription
idreqstringaccountId.
200 OK
{
  "id": "acc_8Kd3...",
  "status": "DISCONNECTED",
  "removed": true,
  "existed": true,
  "billableCount": 0
}
  • ▸Idempotent — removing an already-removed or unknown id returns the same 200 shape (a retried cleanup never fails half-way). existed tells them apart: true when a linked account was actually removed by this call, false when nothing was linked under that id (a typo, or already gone). An id that belongs to another customer is a 404.
  • ▸If the account had always-hot enabled, that per-connection charge stops immediately; billableCount is your remaining always-hot connection count.
  • ▸If the account has a remembered password (see /v1/remember-password), it is destroyed before the account is removed. If our vault can’t confirm that yet, you get 503 VAULT_PURGE_PENDING and the account stays linked; retry the DELETE.
POST/v1/accounts/:id/migrateIdempotency-Key

Switch which terminal type the account presents AS ("MOBILE" or "WEB"). Your open positions, pending orders and balance live on the broker account, not the connection, so they are preserved across the switch.

Path parameters
FieldTypeDescription
idreqstringaccountId.
Request body (JSON)
FieldTypeDescription
toreq"MOBILE" | "WEB"The transport to switch to.
200 OK
{
  "id": "acc_8Kd3...",
  "terminalType": "MOBILE",
  "status": "noop"
}
  • ▸status is "noop" when the account is already on the requested transport.
  • ▸Switching runs only when the account is idle (no in-flight trade — otherwise 409).
  • ▸Both terminal types expose the full surface (account, quotes, positions, history).
  • ▸The switch is zero-gap — the new transport is warmed before the old session is dropped — so open positions, orders and balance carry over untouched.
POST/v1/accounts/:id/migrate-keyIdempotency-Key

Move a broker account between the shared pool and an isolated API key, in place. Pass an isolated API key id to move the account under that key (only that key, plus the dashboard, then sees it), or null to move it back to the shared pool. The connection is never dropped — a warm account stays warm, positions/orders/balance untouched.

Path parameters
FieldTypeDescription
idreqstringaccountId.
Request body (JSON)
FieldTypeDescription
ownerApiKeyIdreqstring | nullAn isolated API key id to move the account into that key, or null for the shared pool.
200 OK
{
  "id": "acc_8Kd3...",
  "ownerApiKeyId": "key_7Qf2...",
  "isolated": true,
  "hot": true,
  "migrated": true
}
  • ▸ownerApiKeyId in the response is the new owner: null = shared pool, else the isolated key id. isolated mirrors it.
  • ▸migrated is false when the account was already where you asked (a no-op).
  • ▸hot reflects the live connection — migration never drops it, so a warm account stays warm.
  • ▸Only the shared pool ↔ an isolated key are valid moves; moving directly between two isolated keys is rejected (migrate to the shared pool first).
  • ▸The account now also carries ownerApiKeyId on GET /v1/accounts and GET /v1/accounts/:id.
  • ▸Requires a full (non read-only) key; via an API key, the only isolated target is the calling key itself — naming another key id requires the dashboard.
PATCH/v1/accounts/:id/always-hotIdempotency-Key

Keep one account’s connection warm 24/7: skips the idle cool-down so the session stays up between your calls. Broker-side drops reconnect automatically on every account regardless; always-hot only removes the idle timeout. Off by default. Trader ($0.99/mo per connection) or Pro / Enterprise (included).

Path parameters
FieldTypeDescription
idreqstringaccountId.
Request body (JSON)
FieldTypeDescription
enabledreqbooleantrue to pin the connection warm, false to let it cool after the idle timeout.
200 OK
{
  "id": "acc_8Kd3...",
  "alwaysHot": true,
  "effective": true,
  "appliedLive": true,
  "billableCount": 1
}
  • ▸effective is the resolved state (this flag OR the account-wide switch, on an eligible plan). appliedLive is true when the account was warm and the change took effect on the live session immediately; a cold account applies it on its next connect.
  • ▸What always-hot does NOT do: it does not store your password. If TickerAll itself restarts, the account returns as needs-rearm (see GET /v1/always-hot/pending) until you POST /v1/sessions again — unless you opt in to remembered credentials.
  • ▸403 ALWAYS_HOT_TIER_REQUIRED on the Free plan.
PATCH/v1/always-hotIdempotency-Key

Account-wide always-hot: every eligible connection defaults to warm. Same semantics as the per-account toggle, applied live to all held sessions.

Request body (JSON)
FieldTypeDescription
enabledreqbooleantrue to keep every connection warm, false to return to per-account flags.
200 OK
{
  "accountWide": true,
  "billableCount": 3
}
  • ▸403 ALWAYS_HOT_TIER_REQUIRED on the Free plan.
GET/v1/always-hot/pending

Always-hot accounts that currently have no live connection and need a credentials refresh — typically after a TickerAll maintenance restart dropped the in-memory password. Empty means nothing needs attention.

200 OK
{
  "pending": [
    { "id": "acc_8Kd3...", "broker": "mt5", "server": "Exness-MT5Trial14", "accountNumber": "****5678" }
  ]
}
  • ▸Re-arm an account by calling POST /v1/sessions with its credentials again. The TypeScript and Python SDKs do this for you: sessions.keepAlive() / keep_alive() caches the credentials in your process and re-arms a cold account on its next call.
  • ▸If you cannot keep credentials on your side at all, see PATCH /v1/remember-password — with it on, accounts that are always-hot re-arm themselves server-side and never appear here.
GET/v1/remember-password

Status of remembered credentials — the one opt-in exception to TickerAll’s rule of never storing broker passwords. Available on Pro and Enterprise, for customers who cannot keep credentials on their own side.

200 OK
{
  "accountWide": false,
  "eligible": true,
  "tier": "PRO",
  "remembered": 0,
  "armed": 0
}
  • ▸remembered = accounts whose password is sealed in the vault; armed = opted in but not yet captured (the password is captured on each account’s next connect, never through this endpoint).
  • ▸eligible is false on the Free and Trader plans.
PATCH/v1/remember-passwordIdempotency-Key

Turn remembered credentials on or off for ALL your accounts. On: each account’s password is sealed in an encrypted vault (AES-256-GCM) on its next connect and used solely to restore the connection after a TickerAll restart; your consent text is recorded. Off: every stored password is purged immediately.

Request body (JSON)
FieldTypeDescription
enabledreqbooleantrue to opt in (account-wide), false to opt out and purge.
consentTextoptionalstringRequired when enabling: the consent wording you are agreeing to (recorded for audit).
200 OK
{
  "accountWide": true
}
  • ▸Off by default on every plan; nothing is stored until you switch it on. Turning it off returns { "accountWide": false, "forgotten": <n> } after rotating every sealed secret to an unreadable value and clearing the flags.
  • ▸Once on, an account that is also always-hot re-arms itself server-side after a maintenance restart — no POST /v1/sessions needed. If you can supply the password yourself on reconnect, leave it off.
  • ▸A password you pass to POST /v1/sessions on a remembered account updates the stored one; on a remembered account you may also omit password to reconnect.
  • ▸403 REMEMBER_PRO_REQUIRED below Pro.

Symbols

Discover the instruments you can trade on an account, in the broker’s native names.

PATCH/v1/accounts/:id/remember-passwordIdempotency-Key

Choose remembered credentials for ONE account, overriding the account-wide setting. on: remember this account’s password even with the account-wide switch off. off: never remember it, even with the switch on, and destroy what is stored now. inherit: follow the account-wide switch again.

Request body (JSON)
FieldTypeDescription
choicereq'on' | 'off' | 'inherit'The choice for this account.
consentTextoptionalstringRequired for on: the consent wording you are agreeing to (recorded for audit).
200 OK
{
  "id": "cmabc123",
  "rememberChoice": "off",
  "effective": false,
  "stored": false,
  "pendingPurge": 0
}
  • ▸effective = whether this account is remembered once both settings are applied. stored = a password is sealed in the vault right now (for on, it is captured on the account’s next connect, never through this endpoint).
  • ▸pendingPurge = 1 when the vault could not confirm the password was destroyed; retry the same call. off is available on every plan; on is Pro and Enterprise (403 REMEMBER_PRO_REQUIRED).
  • ▸Turning the account-wide switch off resets every on to inherit, so it purges everything. An off set here survives turning the switch back on.
GET/v1/accounts/:id/symbols

List tradeable symbols on the account, in the broker’s native names (pass-through, no normalization). symbols is the full catalog; watched is the subset that is actively streaming live ticks right now.

Path parameters
FieldTypeDescription
idreqstringaccountId.
200 OK
{
  "symbols": ["BTCUSD", "ETHUSD", "EURUSD", "GOLD", "USOILm", "..."],
  "watched": ["BTCUSD", "ETHUSD", "EURUSD"]
}
  • ▸Use a name from symbols when placing orders; subscribe to a name to start it ticking on the WebSocket.
  • ▸symbols is never legitimately empty on a connected account. If TickerAll cannot read the account’s instrument list you get 503 SYMBOL_CATALOG_UNAVAILABLE rather than an empty array — so an empty list can never be mistaken for “no symbols” or for an account-type verdict.
GET/v1/accounts/:id/symbol-specs

Per-symbol trading specs for the account: the volume constraints (min / max / step) for validating an order size before placing it, the price geometry (digits, point, tickSize) and contract maths (contractSize, tickValue, tickValueAccount) for sizing risk per tick, the broker’s stop and freeze levels, and the base, profit (quote) and margin currency for each instrument. MT5 only; an MT4 account returns an empty list.

Path parameters
FieldTypeDescription
idreqstringaccountId.
200 OK
{
  "specs": [
    {
      "name": "EURUSD",
      "volumeMin": 0.01,
      "volumeMax": 200.0,
      "volumeStep": 0.01,
      "specSource": "broker",
      "digits": 5,
      "point": 0.00001,
      "tickSize": 0.00001,
      "contractSize": 100000,
      "tickValue": 1.0,
      "tickValueAccount": 1.0,
      "tickValueCurrency": { "code": "USD", "source": "wire" },
      "stopLevel": 0,
      "freezeLevel": 0,
      "baseCurrency": "EUR",
      "profitCurrency": "USD",
      "marginCurrency": "EUR"
    }
  ]
}
  • ▸volumeStep is the lot increment — round an order size down to it before placing; volumeMin / volumeMax bound the size.
  • ▸profitCurrency is the quote currency (the currency P&L accrues in). Use it — not the account currency — to denote an instrument.
  • ▸specSource is "broker" (authoritative) or "derived" (a best-effort fallback when the broker did not supply the spec). Currency fields are absent when unknown.
  • ▸Price geometry: digits is the quote precision, point is the smallest price increment (10^-digits), and tickSize is the smallest price change the broker accepts — equal to point on every instrument we serve. Use tickSize, not a hard-coded 0.0001 / 0.01, to step a price.
  • ▸Tick value: tickValue is the profit-currency value of a one-point move on 1.00 lot (contractSize x point). tickValueAccount is that same figure converted into the ACCOUNT’s deposit currency, and tickValueCurrency tells you which currency it is in plus where it came from — source "wire" (read from the broker), "heuristic" (derived by us) or "override".
  • ▸Size risk off tickValueAccount, not tickValue: risk = |entry - stop| / tickSize * tickValueAccount * lots. tickValue is in the instrument’s profit currency, which differs from your deposit currency on many symbols — using it directly overstates or understates risk by the cross-rate.
  • ▸Absent means UNKNOWN, never zero. Any of digits, point, tickSize, contractSize, tickValue, tickValueAccount and the currency fields is omitted entirely when the broker’s record did not carry it, so you can tell "unknown" from a real 0 and fall back deliberately. Do NOT default a missing tickValueAccount to contractSize * tickSize — that is the profit-currency figure and is wrong whenever the profit currency is not your deposit currency.
  • ▸stopLevel and freezeLevel are in POINTS, and 0 is real data meaning no restriction (common on floating-spread symbols) — not a missing value. stopLevel is the minimum distance an SL / TP must keep from the current price; inside freezeLevel the broker refuses modifications. Checking stopLevel before a PATCH turns most 422 BROKER_REJECTED stop-distance rejections into a client-side check.
GET/v1/accounts/:id/symbols/:symbol/spec

The full trading spec for ONE symbol — the same object GET /v1/accounts/:id/symbol-specs returns per entry, for a single instrument. Cheaper than the full list when you only need the contract maths for the symbol you are about to trade: a warm account answers from its cached catalog. MT5 only; an MT4 account returns 404.

Path parameters
FieldTypeDescription
idreqstringaccountId.
symbolreqstringBroker-native symbol name, e.g. "EURUSD". Discover names via GET /v1/accounts/:id/symbols.
200 OK
{
  "spec": {
    "name": "EURUSD",
    "volumeMin": 0.01,
    "volumeMax": 200.0,
    "volumeStep": 0.01,
    "specSource": "broker",
    "digits": 5,
    "point": 0.00001,
    "tickSize": 0.00001,
    "contractSize": 100000,
    "tickValue": 1.0,
    "tickValueAccount": 1.0,
    "tickValueCurrency": { "code": "USD", "source": "wire" },
    "stopLevel": 0,
    "freezeLevel": 0,
    "baseCurrency": "EUR",
    "profitCurrency": "USD",
    "marginCurrency": "EUR"
  }
}
  • ▸Field meanings, units and the absent-means-unknown contract are identical to GET /v1/accounts/:id/symbol-specs — see its notes, including the risk formula and the warning against defaulting tickValueAccount.
  • ▸A symbol the account has never touched is primed once and then read, so the first call for it can be slower than subsequent ones.
  • ▸An unknown symbol on this account is a 404 — distinct from a known symbol with fields missing, which is a 200 with those fields absent.
GET/v1/accounts/:id/symbols/:symbol/quote

The current live bid and ask for one symbol. Candles are bid-only, so this is the way to read the ASK over REST — needed to compute a spread or to place a limit relative to the offer. MT5 only; an MT4 account returns 404.

Path parameters
FieldTypeDescription
idreqstringaccountId.
symbolreqstringBroker-native symbol name, e.g. "EURUSD".
200 OK
{
  "symbol": "EURUSD",
  "bid": 1.07241,
  "ask": 1.07249
}
  • ▸bid and ask are null — never 0 — when the symbol exists but is not currently quoted, e.g. a closed market. That is an honest "no live price", distinct from an unknown symbol (404).
  • ▸A symbol the account is not already streaming is subscribed once and polled briefly for its first tick, so the first call can take a moment; a watched symbol answers from the last tick immediately.
  • ▸The response carries no tick time — it is the latest tick as of the request. If you need to stamp a price, use your own receive time, or the WebSocket stream.
  • ▸For continuous prices use the WebSocket rather than polling this endpoint.

Candles & history

Historical OHLC bars are included on every plan — no extra charge. The public endpoint GET /v1/public/candles (no API key required) returns bars at any of nine timeframes: M1, M5, M15, M30, H1, H4, D1, W1, MN1. Coarser timeframes reach further back — daily bars reach back years; a single request returns as much as fits in a few seconds.

The authed GET /v1/accounts/:id/candles takes either a look-back window (hours) or an exact date range (from + to, ISO-8601), and every response reports how much of the requested window was actually served via coverage and truncated.

GET/v1/accounts/:id/candles

Fetch historical OHLC bars from your connected broker, for any symbol the broker streams on this account. Two modes: a look-back window (hours) returns the most-recent N hours of bars, or a date range (from + to, ISO-8601) returns the exact [from, to] window. Coarser timeframes (H4, D1, W1, MN1) reach much further back — daily bars typically cover years of history. Every response reports how much of the requested window was actually served (coverage / truncated).

Path parameters
FieldTypeDescription
idreqstringaccountId.
Query parameters
FieldTypeDescription
symbolreqstringBroker-native symbol name, e.g. "BTCUSD". Discover names via GET /v1/accounts/:id/symbols.
hoursoptionalnumberLook-back mode: how many hours of data to return, counted backwards from now. Defaults to 24, capped at ~5 years. Ignored when from and to are supplied.
fromoptionalstring (ISO-8601)Date-range mode: start of the window, e.g. "2026-01-01T00:00:00Z". Pass BOTH from and to (an alternative to hours) to fetch the exact [from, to] window. Supplying only one is a 400 invalid_range.
tooptionalstring (ISO-8601)Date-range mode: end of the window. Required with from and must be after it. When both are present, hours is ignored.
timeframeoptional"M1"|"M5"|"M15"|"M30"|"H1"|"H4"|"D1"|"W1"|"MN1"Bar interval. Defaults to "M5". Coarser timeframes go back further for the same window.
200 OK
{
  "symbol": "BTCUSD",
  "hours": 17520,
  "timeframe": "D1",
  "candles": [
    { "timestamp": 1747699200, "open": 104200.10, "high": 107840.55, "low": 103520.00, "close": 106910.73, "bid": 106910.73 },
    { "timestamp": 1747785600, "open": 106910.73, "high": 108100.00, "low": 105480.20, "close": 107512.40, "bid": 107512.40 }
  ],
  "served": { "from": "2025-05-20T00:00:00.000Z", "to": "2025-05-21T00:00:00.000Z" },
  "count": 2,
  "coverage": "complete",
  "truncated": false,
  "stopReason": "floor"
}
  • ▸Authed — needs your API key (same as the rest of the customer API). Works for any symbol your broker exposes on the connected account.
  • ▸Two modes: pass hours for a look-back window, OR pass both from and to (ISO-8601) for an exact date range. When from and to are present, hours is ignored, and the response echoes from/to (the requested window) at the top level instead of hours. Supplying only one of from/to, an unparseable date, or from ≥ to returns 400 invalid_range with a message.
  • ▸Each candle is { timestamp, open, high, low, close, bid, tickVolume, spread }. timestamp is the bar OPEN time in Unix seconds in BROKER SERVER TIME — not UTC (the same frame the MetaTrader terminal charts in; subtract the account serverTimeOffsetSeconds to get UTC); bid mirrors close. tickVolume (tick count) and spread (price units) are present on recent bars; deep-history bars are bid-only and may omit them (null/absent) — don’t rely on a guaranteed volume.
  • ▸Every response also reports completeness: served ({ from, to } actually returned in ISO-8601, or null when empty), count (number of candles), coverage ("complete" when the whole window was served, otherwise "floor"/"partial"), truncated (boolean — true when the whole requested window was NOT served; the authoritative completeness signal), and stopReason (why the walk stopped, e.g. "floor" = no deeper data exists, "deadline" = worth retrying, "unsupported", "error"). A note field is added when stopReason is "unsupported".
  • ▸A range larger than the per-request cap (~5 years, or ~100,000 bars at the chosen timeframe) is rejected with 400 range_too_large ({ maxBars, estimatedBars, maxWindowDays }) — narrow the range or coarsen the timeframe.
  • ▸Timeframes: M1, M5, M15, M30, H1, H4, D1, W1, MN1. Daily and coarser reach back years; intraday (M1–H4) covers recent months. One request returns as much history as fits in a few seconds. Deep look-backs are isolated onto a dedicated history connection — a big walk never disturbs your live tick stream.
  • ▸If the broker returns no decodable bars for the symbol/range (e.g. an illiquid pair), the response is a 200 with an empty candles array (served null, truncated true) — never a 500.
GET/v1/public/candles

Unauthenticated read of the always-on demo feed — powers the sparklines and chart on tickerall.com. Limited to TickerAll’s demo symbol list and only callable from tickerall.com (origin-gated). For arbitrary broker symbols from your own code, use GET /v1/accounts/:id/candles above.

Query parameters
FieldTypeDescription
symbolreqstringOne of the demo feed’s symbols (e.g. BTCUSD, ETHUSD, EURUSD, GOLD).
hoursoptionalnumberHow many hours of data to return. Defaults to 24, capped at ~5 years.
timeframeoptional"M1"|"M5"|"M15"|"M30"|"H1"|"H4"|"D1"|"W1"|"MN1"Bar interval. Defaults to "M5". Coarser timeframes go back further for the same hours value.
200 OK
{
  "symbol": "BTCUSD",
  "hours": 17520,
  "timeframe": "D1",
  "candles": [
    { "timestamp": 1747699200, "open": 104200.10, "high": 107840.55, "low": 103520.00, "close": 106910.73, "bid": 106910.73 },
    { "timestamp": 1747785600, "open": 106910.73, "high": 108100.00, "low": 105480.20, "close": 107512.40, "bid": 107512.40 }
  ]
}
  • ▸No API key needed, but origin-gated: this endpoint accepts requests from tickerall.com only and exists to drive the public demo widgets. For programmatic access from your own code use GET /v1/accounts/:id/candles instead — it works on any symbol your broker exposes.
  • ▸Each candle is { timestamp, open, high, low, close, bid }. timestamp is the bar OPEN time in Unix seconds in BROKER SERVER TIME — not UTC (the same frame the MetaTrader terminal charts in; subtract the account serverTimeOffsetSeconds to get UTC); bid mirrors close.
  • ▸Only the demo feed’s symbol list is available here. For any other symbol, connect a broker account and call GET /v1/accounts/:id/candles instead.

Orders

Place market or pending orders, close positions (full or partial), and modify stop-loss / take-profit. All three require an Idempotency-Key header.

POST/v1/accounts/:id/ordersIdempotency-Key

Place a market order (fills immediately) or a pending limit/stop order (rests until price is hit).

Path parameters
FieldTypeDescription
idreqstringaccountId.
Request body (JSON)
FieldTypeDescription
typereq"market" | "limit" | "stop"The base order type — market (fills now) or limit / stop (rests until price is hit). Do NOT pass MetaTrader-style combined names like "BUY_STOP" here: those are only ever returned by GET /orders. On input, split the intent into type + side — e.g. a buy-stop is type:"stop" + side:"BUY".
symbolreqstringBroker-native symbol, e.g. "BTCUSD".
sidereq"BUY" | "SELL"Trade direction — uppercase BUY or SELL. Combined with type it expresses the order, e.g. type:"limit" + side:"SELL" is a sell-limit.
volumereqnumberLots, e.g. 0.10. Must be positive.
priceoptionalnumberTrigger price. Required for limit and stop; ignored for market.
stopLossoptionalnumberStop-loss price. Omit for none; 0 means the same thing.
takeProfitoptionalnumberTake-profit price. Omit for none; 0 means the same thing.
commentoptionalstring (≤ 31 chars)Optional strategy tag stored on the order.
201 Created
{
  "ticket": 4072808150,
  "symbol": "BTCUSD",
  "side": "BUY",
  "type": "market",
  "volume": 0.10,
  "price": null,
  "stopLoss": 71000,
  "takeProfit": 84000,
  "comment": "my-strategy",
  "status": "open",
  "timestamp": "2026-05-22T17:55:03.000Z"
}
  • ▸Requires an Idempotency-Key header (see Conventions). Re-sending the same key returns the original response without placing a second order.
  • ▸Pending orders are expressed as two separate fields, not one combined name: a buy-stop is type:"stop" + side:"BUY"; a sell-limit is type:"limit" + side:"SELL". The write API does NOT accept MetaTrader-style combined names (BUY_STOP, SELL_LIMIT, …) — passing type:"BUY_STOP" is a 400 VALIDATION_ERROR. GET /v1/accounts/:id/orders reports that combined name in its own type field for readability, but that is a read-only convenience: do not echo it back here — use its orderType (LIMIT/STOP) lowercased as type, plus side.
  • ▸status is "open" for market orders and "pending" for limit/stop orders.
  • ▸A broker rejection (bad volume, market closed, stop-level too tight, insufficient margin) comes back as 422 BROKER_REJECTED with the broker’s reason in message.
  • ▸Pending orders are placed good-till-cancelled and there is no expiry field on this call. An expiry you cannot set is better than one silently ignored: nothing here accepts an expiration time, so a pending order rests until it triggers or you cancel it with DELETE /v1/accounts/:id/orders/:ticket.
GET/v1/accounts/:id/orders

List the account’s working pending orders — LIMIT and STOP orders resting until their trigger price is hit. A market order is never pending: it becomes an open position immediately (see GET /v1/accounts/:id/positions). Returns an empty list when nothing is resting.

Path parameters
FieldTypeDescription
idreqstringaccountId.
200 OK
{
  "orders": [
    {
      "ticket": "4072809988",
      "symbol": "BTCUSD",
      "type": "BUY_LIMIT",
      "side": "BUY",
      "orderType": "LIMIT",
      "volume": 0.10,
      "price": 68000,
      "limitPrice": null,
      "stopLoss": 66000,
      "takeProfit": 72000,
      "setTime": "2026-08-14T09:12:44.000Z",
      "expirationTime": null
    }
  ]
}
  • ▸Working (pending) orders only. When a pending order triggers it leaves this list and becomes an open position — find it under GET /v1/accounts/:id/positions.
  • ▸type is one of BUY_LIMIT, SELL_LIMIT, BUY_STOP, SELL_STOP, BUY_STOP_LIMIT, SELL_STOP_LIMIT; orderType collapses that to LIMIT / STOP / STOP_LIMIT and side to BUY / SELL. ticket is a string; price is the trigger (activation) level; limitPrice is set only on STOP_LIMIT variants; expirationTime is null for good-till-cancelled.
  • ▸Expiry is READ-ONLY today. Every pending order placed through this API is good-till-cancelled, so expirationTime is null on anything you placed here; a non-null value means the order was created elsewhere (your terminal) with an expiry. There is no expiry field on POST /v1/accounts/:id/orders or PATCH /v1/accounts/:id/orders/:ticket — expiry is not accepted and therefore never silently dropped. Cancel explicitly with DELETE /v1/accounts/:id/orders/:ticket when you need a time-bounded order.
  • ▸This combined type (e.g. BUY_STOP) is READ-ONLY. To place or modify an order, use the split form — type (market/limit/stop) + side (BUY/SELL) — from POST /v1/accounts/:id/orders; the write API does not accept BUY_STOP-style names.
  • ▸For live updates instead of polling, subscribe to the orders channel over the WebSocket (see the WebSocket section) — it pushes the full pending-order list on subscribe and on every change.
  • ▸Optional ?waitMs=<0–10000> adds a settle window for a just-warmed connection’s first pending snapshot; omit for an immediate read.
  • ▸Also reachable at the alias GET /v1/accounts/:id/orders/pending.
DELETE/v1/accounts/:id/orders/:ticketIdempotency-Key

Cancel a resting pending order (LIMIT or STOP) by its ticket.

Path parameters
FieldTypeDescription
idreqstringaccountId.
ticketreqnumberTicket of the pending order to cancel (from GET /v1/accounts/:id/orders).
200 OK
{
  "ticket": 4072809988,
  "symbol": "BTCUSD",
  "side": "BUY",
  "cancelled": true,
  "timestamp": "2026-08-14T09:12:44.000Z"
}
  • ▸Requires an Idempotency-Key header.
  • ▸ticket is the pending order’s ticket from GET /v1/accounts/:id/orders — not a position. If the order has already triggered it is now an open position; close it with DELETE /v1/accounts/:id/positions/:ticket instead.
  • ▸If the ticket is not a resting pending order on this account you get 404 TICKET_NOT_FOUND.
  • ▸Pending-order management is an MT5 feature; on an account that does not support it the call returns 400.
PATCH/v1/accounts/:id/orders/:ticketIdempotency-Key

Modify a resting pending order’s trigger price, stop-loss or take-profit. Any field you omit is preserved at its current value.

Path parameters
FieldTypeDescription
idreqstringaccountId.
ticketreqnumberTicket of the pending order to modify (from GET /v1/accounts/:id/orders).
Request body (JSON)
FieldTypeDescription
priceoptionalnumberNew trigger (activation) price. Omit to keep the current trigger.
stopLossoptionalnumberNew stop-loss. Send 0 to REMOVE it. Omit to keep the current SL.
takeProfitoptionalnumberNew take-profit. Send 0 to REMOVE it. Omit to keep the current TP.
200 OK
{
  "ticket": 4072809988,
  "symbol": "BTCUSD",
  "side": "BUY",
  "price": 66000,
  "stopLoss": 64000,
  "takeProfit": 72000,
  "timestamp": "2026-08-14T09:12:44.000Z"
}
  • ▸Requires an Idempotency-Key header.
  • ▸Provide at least one of price, stopLoss or takeProfit — an empty body is a 400. Omitted fields keep their current value.
  • ▸If the ticket is not a resting pending order on this account you get 404 TICKET_NOT_FOUND.
  • ▸As on an open position, send stopLoss or takeProfit as 0 to REMOVE that level rather than move it; omitting the field keeps it. The trigger price is different — price must be a positive number, since a resting order has nowhere to trigger without one.
  • ▸This call changes the trigger price, SL and TP only — it cannot set or remove an expiry. See GET /v1/accounts/:id/orders on expiry being read-only.
POST/v1/accounts/:id/orders/cancel-allIdempotency-Key

Cancel every resting pending order on the account in one call, optionally narrowed by symbol, side and/or type. The server lists the pending orders once and cancels the matches in parallel.

Path parameters
FieldTypeDescription
idreqstringaccountId.
Request body (JSON)
FieldTypeDescription
symboloptionalstringOnly cancel pending orders on this broker-native symbol (e.g. BTCUSDm). Omit to cancel on every symbol. Case-insensitive.
sideoptionalstringOnly cancel BUY or SELL pending orders. Omit for both sides.
typeoptionalstringOnly cancel this order type — limit, stop or stopLimit. Omit for all types.
200 OK
{
  "results": [
    { "ticket": 4072809988, "symbol": "BTCUSD", "side": "BUY", "status": "ok", "cancelled": true },
    { "ticket": 4072809991, "symbol": "BTCUSD", "side": "SELL", "status": "failed", "code": "BROKER_REJECTED", "reason": "Broker rejected the cancel." }
  ],
  "summary": { "requested": 2, "ok": 1, "failed": 1 }
}
  • ▸Requires an Idempotency-Key header.
  • ▸Body (all fields optional): { "symbol": "EURUSD", "side": "BUY", "type": "limit" }. type is one of limit / stop / stopLimit; side is BUY / SELL.
  • ▸Single-account cancel — for a ticket you already hold use DELETE /v1/accounts/:id/orders/:ticket; to cancel across many accounts at once use POST /v1/bulk/orders/cancel.
  • ▸Always 200. No matching pending orders is an empty success — results: [] with summary { requested: 0, ok: 0, failed: 0 } — never a 404 or an error.
  • ▸Partial success — each order’s outcome is in results: status "ok" (cancelled true) or "failed" (with a code and reason). summary tallies requested / ok / failed.
  • ▸Pending-order management is an MT5 feature; on an account that does not support it the call returns 400.
DELETE/v1/accounts/:id/positions/:ticketIdempotency-Key

Close an open position. Omit volume for a full close, or pass a smaller volume for a partial close.

Path parameters
FieldTypeDescription
idreqstringaccountId.
ticketreqnumberTicket of the position to close (from the order response or GET /v1/accounts/:id).
Request body (JSON)
FieldTypeDescription
volumeoptionalnumberPartial-close volume in lots. If omitted, the whole position is closed.
200 OK
{
  "ticket": 4072808150,
  "symbol": "BTCUSD",
  "side": "BUY",
  "volume": 0.10,
  "closed": true,
  "timestamp": "2026-05-22T18:10:44.000Z",
  "closePrice": 81320.15,
  "dealTicket": "1191098644",
  "profit": -0.40,
  "swap": 0,
  "commission": 0
}
  • ▸Requires an Idempotency-Key header. Re-using a key with a different body or ticket is a 409 IDEMPOTENCY_KEY_REUSED — nothing is replayed or executed.
  • ▸If the ticket is not an open position on this account you get 404 TICKET_NOT_FOUND.
  • ▸closePrice, dealTicket, profit, swap and commission are the booked closing deal — the same values the OUT row in /deals-history carries (dealTicket is its ticket there; money in the account currency). They are null when the broker has not booked the deal by the time the response is sent (rare — read /deals-history then); 0 is a real 0. A partial close reports the deal for the closed part.
  • ▸volume is what was actually closed. Asking for more than is open closes the whole position (the contract is unchanged) and the response then also carries requestedVolume, warningCode "VOLUME_CLAMPED" and a warning message; these three fields are absent otherwise.
POST/v1/accounts/:id/positions/close-allIdempotency-Key

Flatten the account — close every open position in one call, optionally narrowed by symbol and/or side. The server reads the account’s open positions once and closes the matches in parallel.

Path parameters
FieldTypeDescription
idreqstringaccountId.
Request body (JSON)
FieldTypeDescription
symboloptionalstringOnly close positions on this broker-native symbol (e.g. BTCUSDm). Omit to close on every symbol. Case-insensitive.
sideoptionalstringOnly close BUY or SELL positions. Omit to close both sides.
200 OK
{
  "results": [
    { "ticket": 4072808150, "symbol": "BTCUSD", "side": "BUY", "status": "ok", "closed": true, "closePrice": 81320.15, "dealTicket": "1191098644", "profit": -0.40, "swap": 0, "commission": 0 },
    { "ticket": 4072809002, "symbol": "BTCUSD", "side": "SELL", "status": "failed", "code": "BROKER_REJECTED", "reason": "Broker rejected the close." }
  ],
  "summary": { "requested": 2, "ok": 1, "failed": 1 }
}
  • ▸Requires an Idempotency-Key header.
  • ▸Body (all fields optional): { "symbol": "EURUSD", "side": "BUY" }. side is BUY / SELL.
  • ▸Each "ok" result carries the booked closing deal (closePrice, dealTicket, profit, swap, commission), null when not yet booked — the same fields as a single DELETE.
  • ▸Single-account flatten — for a ticket you already hold use DELETE /v1/accounts/:id/positions/:ticket; to flatten across many accounts at once use POST /v1/bulk/positions/close.
  • ▸Always 200. No matching positions is an empty success — results: [] with summary { requested: 0, ok: 0, failed: 0 } — never a 404 or an error.
  • ▸Partial success — each position’s outcome is in results: status "ok" (closed true) or "failed" (with a code and reason). summary tallies requested / ok / failed.
  • ▸Each "ok" result carries the booked close deal (closePrice, dealTicket, profit, swap, commission), as on the single close; null until the broker books it.
PATCH/v1/accounts/:id/positions/:ticketIdempotency-Key

Modify the stop-loss and/or take-profit of an open position, or remove one. Provide at least one of stopLoss / takeProfit; send 0 to clear a level rather than move it.

Path parameters
FieldTypeDescription
idreqstringaccountId.
ticketreqnumberTicket of the position to modify.
Request body (JSON)
FieldTypeDescription
stopLossoptionalnumberNew stop-loss price. Send 0 to REMOVE the stop-loss. Omit to leave it unchanged.
takeProfitoptionalnumberNew take-profit price. Send 0 to REMOVE the take-profit. Omit to leave it unchanged.
200 OK
{
  "ticket": 4072808150,
  "symbol": "BTCUSD",
  "side": "BUY",
  "volume": 0.10,
  "stopLoss": 72000,
  "takeProfit": 85000,
  "timestamp": "2026-05-22T18:12:09.000Z"
}
  • ▸Requires an Idempotency-Key header.
  • ▸You must supply at least one of stopLoss or takeProfit; sending neither is a VALIDATION_ERROR.
  • ▸Some brokers enforce a minimum stop distance (stop level). Too-tight values come back as 422 BROKER_REJECTED. GET /v1/accounts/:id/symbol-specs reports that distance as stopLevel, in points, so you can check it before sending.
  • ▸To CLEAR a protective level, send it as 0 — that removes it. 0 and "omitted" are different instructions: 0 means remove this level, omitting the field means leave it exactly as it is. So { "stopLoss": 0 } removes the stop-loss and keeps the take-profit untouched, and { "stopLoss": 0, "takeProfit": 0 } removes both. 0 also satisfies the at-least-one-field rule, so clearing is never an empty body.
  • ▸The response and GET /v1/accounts/:id/positions both report a removed level as 0, not null — the same 0 you send. A position that never had an SL or TP reads 0 as well, so 0 consistently means "no level" in both directions.

Trade history

Your account’s closed-trade history — executed trades paired into round-trips (entry + exit) with realised P/L, the equivalent of MT5’s history_deals_get. Returns the recent window your broker provides on connect plus anything closed live during the session; filter by symbol and close-time range.

GET/v1/accounts/:id/history

Closed-trade history for the account — executed trades paired into round-trips (entry + exit), the equivalent of MT5’s history_deals_get. Returns the recent window your broker provides on connect plus any trades closed live during the session. Filter by symbol and close-time range.

Path parameters
FieldTypeDescription
idreqstringaccountId.
Query parameters
FieldTypeDescription
symboloptionalstringNarrow to one broker-native symbol, e.g. "ETHUSD". Omit for all symbols.
fromoptionalISO-8601 | epoch secondsOnly trades closed at/after this time. A value that does not parse is 400 INVALID_DATE.
tooptionalISO-8601 | epoch secondsOnly trades closed at/before this time.
limitoptionalnumberRows per page, newest-first. Defaults to 500, capped at 5000.
cursoroptionalstringThe nextCursor from the previous page, passed back unchanged to get the next (older) page. Omit for the newest page. A malformed cursor, or one from another endpoint, is 400 INVALID_CURSOR.
waitMsoptionalnumberHow long to wait (ms) for history to populate on a just-connected account. Defaults to 4000; pass 0 to skip the wait on a warm connection.
200 OK
{
  "trades": [
    {
      "ticket": "4072808150",
      "symbol": "ETHUSD",
      "side": "BUY",
      "volume": 0.10,
      "openPrice": 2500.50,
      "closePrice": 2510.25,
      "openTime": "2026-05-20T10:00:00.000Z",
      "closeTime": "2026-05-20T12:30:00.000Z",
      "brokerOpenTime": "2026-05-20T13:00:00+03:00",
      "brokerCloseTime": "2026-05-20T15:30:00+03:00",
      "profit": 0.98,
      "swap": -0.12,
      "commission": 0,
      "stopLoss": 0,
      "takeProfit": 0,
      "closeTicket": "4072808151",
      "complete": true
    }
  ],
  "count": 1,
  "limit": 500,
  "hasMore": false,
  "nextCursor": null,
  "serverTimeOffsetSeconds": 10800
}
  • ▸openTime / closeTime are UTC. brokerOpenTime / brokerCloseTime are the same instants in the broker’s server-local time (RFC 3339 with offset), and serverTimeOffsetSeconds is that offset, measured from the broker’s own records. null means it could not be derived yet (no deal or open position on the account, or MT4); 0 means a UTC server.
  • ▸Returns what the broker provides for the account on connect plus trades closed live during the current session — how far back that reaches is set by the broker. from/to filter those rows; they do not fetch beyond what the broker provides.
  • ▸Each row is a round-trip: ticket is the open/position ticket; closeTicket is the closing deal (may be null for a trade closed live this session). profit is realised P/L in the account currency.
  • ▸Open positions are never listed here — they live under GET /v1/accounts/:id/positions. complete=true is a confirmed round-trip with trustworthy P/L; complete=false marks a closed row we could not pair with its opening deal, where profit is reported as null rather than a misleading 0.
  • ▸A partially closed position that still has volume open is listed as one row with partial: true: volume is the closed volume so far, remainingVolume is the volume still open, profit/swap/commission are the broker-booked figures for the closed part, and complete is false. When the rest closes, that row is replaced by a single row with the same ticket, the full volume, complete: true and no partial or remainingVolume fields.
  • ▸swap is provided: the swap the broker booked on the position, in the account currency (non-zero for positions held across a rollover). commission is the commission the broker charged on the round-trip (opening plus closing deal), in the account currency, stored as the broker books it: negative for a charge, 0 on account types that charge none. A trade opened and closed within the current session shows its commission within about 30 seconds of the close (the ledger is re-read on the next history read after a balance change)
  • ▸A cheap read that never disturbs your live tick stream. On a connection that has cooled you get 409 BROKER_ACCOUNT_NOT_HOT — POST /v1/sessions to reconnect.
GET/v1/accounts/:id/deals-history

Individual deal fills for the account — one row per executed deal, the equivalent of MT5’s "Deals" view (while /history is the aggregated one-row-per-position "Positions" view). Each entry — the open, and every PARTIAL and full close — is its own row with its own ticket, time, price and volume. Filter by symbol and time range.

Path parameters
FieldTypeDescription
idreqstringaccountId.
Query parameters
FieldTypeDescription
symboloptionalstringNarrow to one broker-native symbol, e.g. "BTCUSDm". Omit for all symbols.
fromoptionalISO-8601 | epoch secondsOnly deals executed at/after this time. A value that does not parse is 400 INVALID_DATE.
tooptionalISO-8601 | epoch secondsOnly deals executed at/before this time.
limitoptionalnumberRows per page, newest-first. Defaults to 500, capped at 5000.
cursoroptionalstringThe nextCursor from the previous page, passed back unchanged to get the next (older) page. Omit for the newest page. A malformed cursor, or one from another endpoint, is 400 INVALID_CURSOR.
waitMsoptionalnumberHow long to wait (ms) for history to populate on a just-connected account. Defaults to 4000; pass 0 to skip the wait on a warm connection.
200 OK
{
  "deals": [
    {
      "ticket": "1952757330",
      "positionId": "2075644599",
      "symbol": "BTCUSDm",
      "side": "SELL",
      "entry": "OUT",
      "volume": 0.05,
      "price": 81390.12,
      "stopLoss": 0,
      "takeProfit": 0,
      "profit": -9.27,
      "swap": 0,
      "commission": -0.40,
      "time": "2026-10-18T14:21:10.000Z",
      "brokerTime": "2026-10-18T17:21:10+03:00"
    }
  ],
  "count": 1,
  "limit": 500,
  "hasMore": false,
  "nextCursor": null,
  "serverTimeOffsetSeconds": 10800
}
  • ▸One row per DEAL (fill), not per position: a position opened and then closed in two parts is three rows — the open (entry "IN") and the two closing deals (entry "OUT"), each with its own ticket, time, price and volume. Use this when you need the individual exits; use GET /v1/accounts/:id/history for the aggregated one-row-per-position view (an MT5 terminal shows both the same way, as "Deals" and "Positions").
  • ▸entry is the deal’s role: "IN" opened/added, "OUT" closed/reduced, "INOUT" a reversal, "OUT_BY" closed by an opposing position. positionId ties every deal to the position it belongs to; ticket is the deal’s own id.
  • ▸profit / swap / commission are the broker-booked realised figures on a CLOSE ("OUT") fill, in the account currency — so a partial exit shows its own exact realised P/L. They are ABSENT (not 0) on an "IN" / open fill, which books no P/L — treat absent as "not booked", never as 0.
  • ▸time is UTC; brokerTime is the same instant in the broker’s server-local time (RFC 3339 with offset), and serverTimeOffsetSeconds is that offset — null if not derivable yet, 0 for a UTC server.
  • ▸Trade fills only — deposits, withdrawals and other balance-ledger entries are NOT here; they live on GET /v1/accounts/:id/balance-operations.
  • ▸Paged: while hasMore is true, call again with cursor=nextCursor (same filters) for the next older page; every row comes back exactly once, in order. nextCursor is null on the last page.
GET/v1/accounts/:id/orders-history

Historical ORDERS for the account — one row per order that reached a terminal state (filled, cancelled, expired, rejected), the equivalent of MT5’s history orders. Distinct from /deals-history (the individual fills) and /history (the aggregated round-trips): this is the order-lifecycle view. Filter by symbol and time range.

Path parameters
FieldTypeDescription
idreqstringaccountId.
Query parameters
FieldTypeDescription
symboloptionalstringNarrow to one broker-native symbol, e.g. "BTCUSDm". Omit for all symbols.
fromoptionalISO-8601 | epoch secondsOnly orders at/after this time. A value that does not parse is 400 INVALID_DATE.
tooptionalISO-8601 | epoch secondsOnly orders at/before this time.
limitoptionalnumberRows per page, newest-first. Defaults to 500, capped at 5000.
cursoroptionalstringThe nextCursor from the previous page, passed back unchanged to get the next (older) page. Omit for the newest page. A malformed cursor, or one from another endpoint, is 400 INVALID_CURSOR.
waitMsoptionalnumberHow long to wait (ms) for history to populate on a just-connected account. Defaults to 4000; pass 0 to skip the wait on a warm connection.
200 OK
{
  "orders": [
    {
      "orderTicket": "2075644598",
      "positionId": "2075644599",
      "symbol": "BTCUSDm",
      "side": "BUY",
      "volume": 0.10,
      "price": 81555.00,
      "state": "FILLED",
      "dealCount": 1,
      "time": "2026-10-18T14:20:02.000Z",
      "brokerTime": "2026-10-18T17:20:02+03:00"
    }
  ],
  "count": 1,
  "limit": 500,
  "hasMore": false,
  "nextCursor": null,
  "serverTimeOffsetSeconds": 10800
}
  • ▸One row per ORDER. orderTicket is the order’s own ticket (distinct from the position ticket); positionId links it to the position it opened or acted on. volume is the order’s volume and price its request / fill price.
  • ▸state is the order’s final ENUM_ORDER_STATE: "FILLED", "CANCELED", "EXPIRED", "REJECTED", "PARTIAL", "PLACED" or "UNKNOWN". dealCount is how many deal fills the order produced (a market order is normally 1).
  • ▸time is UTC; brokerTime is the same instant in the broker’s server-local time (RFC 3339 with offset), and serverTimeOffsetSeconds is that offset — null if not derivable yet, 0 for a UTC server.
  • ▸For the individual fills an order produced, use GET /v1/accounts/:id/deals-history (join on positionId); for the aggregated round-trip with realised P/L, GET /v1/accounts/:id/history. Trade orders only — no balance-ledger entries.
  • ▸Paged: while hasMore is true, call again with cursor=nextCursor (same filters) for the next older page; every row comes back exactly once, in order. nextCursor is null on the last page.
GET/v1/accounts/:id/balance-operations

Balance operations for the account — money moving in or out (deposits, withdrawals, credits, charges, corrections, bonuses), kept separate from trades so a deposit is never summed as P/L. The equivalent of MT5’s DEAL_TYPE_BALANCE family. Newest-first, like every history route — reverse it to fold into an equity curve.

Path parameters
FieldTypeDescription
idreqstringaccountId.
Query parameters
FieldTypeDescription
fromoptionalISO-8601 | epoch secondsOnly operations at/after this time. A value that does not parse is 400 INVALID_DATE.
tooptionalISO-8601 | epoch secondsOnly operations at/before this time.
limitoptionalnumberRows per page, newest-first (limit=1 is the most recent operation). Defaults to 500, capped at 5000.
cursoroptionalstringThe nextCursor from the previous page, passed back unchanged to get the next (older) page. Omit for the newest page. A malformed cursor, or one from another endpoint, is 400 INVALID_CURSOR.
waitMsoptionalnumberHow long to wait (ms) for history to populate on a just-connected account. Defaults to 4000; pass 0 to skip the wait on a warm connection.
200 OK
{
  "operations": [
    {
      "ticket": "4138294581",
      "type": "deposit",
      "dealType": 2,
      "amount": 500,
      "time": "2026-09-12T15:44:08.000Z",
      "brokerTime": "2026-09-12T18:44:08+03:00",
      "account": 434221754
    }
  ],
  "count": 1,
  "limit": 500,
  "hasMore": false,
  "nextCursor": null,
  "serverTimeOffsetSeconds": 10800,
  "windowed": true
}
  • ▸type is one of deposit, withdrawal, credit, charge, correction, bonus, commission, other. deposit / withdrawal are derived from the sign of amount; the rest map the broker’s own ledger kinds. dealType is the broker’s raw ENUM_DEAL_TYPE, passed through so an unrecognised kind is never silently flattened.
  • ▸amount is signed and in the account currency, exactly as the broker booked it: positive = money in, negative = money out.
  • ▸Paged: while hasMore is true, call again with cursor=nextCursor (same filters) for the next older page; every row comes back exactly once, in order. nextCursor is null on the last page.
  • ▸time is UTC; brokerTime is the same instant in the broker’s server-local time (RFC 3339 with offset), and serverTimeOffsetSeconds is that offset. null means it could not be derived yet (no deal or open position on the account, or MT4); 0 means a UTC server.
  • ▸Returns what the broker provides for the account on connect — the same rule as /history. An empty list means no operations in what the broker provided, not that the account was never funded; treat the oldest row as a floor, not the account’s opening.
  • ▸No symbol filter: balance operations are account-level. Realtime: the WebSocket account channel pushes an account_update on every balance change, including deposits and withdrawals — read this endpoint on that push to learn what moved the balance. A new operation is refreshed from the broker on the first read after the balance moved, so it is readable within seconds of the push.
  • ▸A cheap read that never disturbs your live tick stream. On a connection that has cooled you get 409 BROKER_ACCOUNT_NOT_HOT — POST /v1/sessions to reconnect.

Bulk operations

Execute one action across many of your accounts in a single request — place, close, modify, and cancel over a whole set of accounts, or read live state for your entire roster at once. Every bulk call reports each account’s outcome separately: a mix of successes and failures still returns 200, with a per-account results array and a summary tally, so a partial success is never ambiguous.

Bulk operations require a Pro or Enterprise plan; on the Free and Trader plans they return 403. The write calls take the same Idempotency-Key header as their single-account counterparts.

POST/v1/bulk/ordersIdempotency-Key

Place orders across many of your accounts in one request — one call fans out to every account you name, and each order’s outcome is reported separately. An account can appear more than once, so one call can send several legs to the same account. Requires a Pro or Enterprise plan.

Request body (JSON)
FieldTypeDescription
ordersreqobject[]The orders to place — 1 to 50, one object per target: { accountId, type?, symbol, side, volume, price?, stopLoss?, takeProfit?, comment? }. accountId is the account to trade; type defaults to "market" ("limit"/"stop" need a price for a pending order); symbol, side and volume are required; stopLoss / takeProfit / comment are optional. Each entry mirrors the single-account POST /v1/accounts/:id/orders body.
200 OK
{
  "results": [
    { "accountId": "acc_8Kd3...", "status": "filled", "ticket": 4072808150, "price": 77512.73, "symbol": "BTCUSD" },
    { "accountId": "acc_9Lm4...", "status": "failed", "symbol": "BTCUSD", "code": "BROKER_REJECTED", "reason": "Not enough money" }
  ],
  "summary": { "total": 2, "filled": 1, "failed": 1 }
}
  • ▸Requires a Pro or Enterprise plan — on the Free and Trader plans this call returns 403.
  • ▸Requires an Idempotency-Key header (see Conventions). Re-sending the same key replays the original response without placing any order a second time.
  • ▸Partial success — a mix of filled and failed accounts still returns 200. Read each account’s outcome in results: status is "filled" (with its ticket and fill price) or "failed" (with a code and human-readable reason). summary tallies total / filled / failed.
  • ▸Works on both live and demo accounts — there is no account-type restriction beyond the Pro / Enterprise plan requirement above.
  • ▸Several legs on one account — repeat its accountId. results come back in the order you sent them. On one account, market orders run in parallel and pending orders are placed one after another.
POST/v1/bulk/positions/closeIdempotency-Key

Close positions across many accounts in one request — either an explicit list of positions, or a per-account intent that flattens each account (optionally narrowed by its own broker-native symbol or side). Requires a Pro or Enterprise plan.

Request body (JSON)
FieldTypeDescription
itemsoptionalobject[]Explicit mode: the exact positions to close, one object each: { accountId, ticket, volume? }. Omit volume for a full close, or pass a smaller volume for a partial close. Use this OR the intent fields below — not both.
targetsoptionalobject[]Intent mode: a per-account list, one object each: { accountId, symbol?, side? }. Every open position on that account is closed unless narrowed by its own symbol and/or side. Each target carries its own broker-native symbol, so a mixed-broker roster (Exness EURUSDm + XM EURUSD) works in one call. Use this OR items.
200 OK
{
  "results": [
    { "accountId": "acc_8Kd3...", "ticket": 4072808150, "status": "ok", "symbol": "BTCUSD", "side": "BUY", "volume": 0.10, "closed": true, "closePrice": 81320.15, "dealTicket": "1191098644", "profit": -0.40, "swap": 0, "commission": 0 },
    { "accountId": "acc_9Lm4...", "ticket": 4072809002, "status": "failed", "symbol": "BTCUSD", "side": "SELL", "volume": 0.20, "closed": false, "code": "TICKET_NOT_FOUND", "reason": "Position not open" }
  ],
  "summary": { "total": 2, "ok": 1, "failed": 1 }
}
  • ▸Requires a Pro or Enterprise plan — on the Free and Trader plans this call returns 403.
  • ▸Requires an Idempotency-Key header. Re-sending the same key replays the original response without closing anything twice; re-using it with a different body is a 409 IDEMPOTENCY_KEY_REUSED.
  • ▸Each "ok" result carries the booked closing deal (closePrice, dealTicket, profit, swap, commission), null when not yet booked.
  • ▸Two modes: pass items for an explicit per-position list, OR targets for a per-account intent (each { accountId, symbol?, side? }, with its own broker-native symbol). Supplying neither is a 400.
  • ▸Partial success — each position’s outcome is in results: status "ok" (closed true) or "failed" (with a code and reason). summary tallies total / ok / failed.
  • ▸Each "ok" result carries the booked close deal (closePrice, dealTicket, profit, swap, commission), as on the single close; null until the broker books it.
POST/v1/bulk/positions/modifyIdempotency-Key

Modify the stop-loss and/or take-profit on positions across many accounts in one request. Requires a Pro or Enterprise plan.

Request body (JSON)
FieldTypeDescription
itemsreqobject[]The positions to modify, one object each: { accountId, ticket, stopLoss?, takeProfit? }. Provide at least one of stopLoss / takeProfit per item; an omitted field keeps its current value.
200 OK
{
  "results": [
    { "accountId": "acc_8Kd3...", "ticket": 4072808150, "status": "ok", "symbol": "BTCUSD", "side": "BUY", "modified": true },
    { "accountId": "acc_9Lm4...", "ticket": 4072809002, "status": "failed", "symbol": "BTCUSD", "side": "SELL", "modified": false, "code": "BROKER_REJECTED", "reason": "Invalid stops" }
  ],
  "summary": { "total": 2, "ok": 1, "failed": 1 }
}
  • ▸Requires a Pro or Enterprise plan — on the Free and Trader plans this call returns 403.
  • ▸Requires an Idempotency-Key header.
  • ▸Each item must carry at least one of stopLoss / takeProfit; an omitted field is preserved at its current value, and a field sent as 0 REMOVES that level — the same meaning as on the single-account PATCH this fans out to. So { accountId, ticket, stopLoss: 0 } clears the stop on that position and leaves its take-profit alone.
  • ▸Partial success — per-position outcome in results: status "ok" (modified true) or "failed" (with a code and reason). summary tallies total / ok / failed.
POST/v1/bulk/orders/cancelIdempotency-Key

Cancel resting pending orders (LIMIT / STOP) across many accounts in one request — an explicit list, or a per-account intent that cancels the pending orders on each account (optionally narrowed by its own broker-native symbol). Requires a Pro or Enterprise plan.

Request body (JSON)
FieldTypeDescription
itemsoptionalobject[]Explicit mode: the pending orders to cancel, one object each: { accountId, ticket }. Use this OR the intent fields below.
targetsoptionalobject[]Intent mode: a per-account list, one object each: { accountId, symbol? }. Cancels every resting pending order on that account, narrowed by its own broker-native symbol. Use this OR items.
200 OK
{
  "results": [
    { "accountId": "acc_8Kd3...", "ticket": 4072809988, "status": "ok", "cancelled": true },
    { "accountId": "acc_9Lm4...", "ticket": 4072809991, "status": "failed", "cancelled": false, "code": "TICKET_NOT_FOUND", "reason": "Not a resting pending order" }
  ],
  "summary": { "total": 2, "ok": 1, "failed": 1 }
}
  • ▸Requires a Pro or Enterprise plan — on the Free and Trader plans this call returns 403.
  • ▸Requires an Idempotency-Key header.
  • ▸Two modes: items for an explicit list, OR targets for a per-account intent (each { accountId, symbol? }, with its own broker-native symbol). Supplying neither is a 400.
  • ▸Partial success — per-order outcome in results: status "ok" (cancelled true) or "failed" (with a code and reason). summary tallies total / ok / failed.
  • ▸Pending-order management is an MT5 feature; an entry on an account that does not support it comes back failed.
POST/v1/bulk/orders/modifyIdempotency-Key

Modify resting pending orders (trigger price, stop-loss, take-profit) across many accounts in one request. Requires a Pro or Enterprise plan.

Request body (JSON)
FieldTypeDescription
itemsreqobject[]The pending orders to modify, one object each: { accountId, ticket, price?, stopLoss?, takeProfit? }. Provide at least one of price / stopLoss / takeProfit per item; an omitted field keeps its current value.
200 OK
{
  "results": [
    { "accountId": "acc_8Kd3...", "ticket": 4072809988, "status": "ok", "modified": true },
    { "accountId": "acc_9Lm4...", "ticket": 4072809991, "status": "failed", "modified": false, "code": "TICKET_NOT_FOUND", "reason": "Not a resting pending order" }
  ],
  "summary": { "total": 2, "ok": 1, "failed": 1 }
}
  • ▸Requires a Pro or Enterprise plan — on the Free and Trader plans this call returns 403.
  • ▸Requires an Idempotency-Key header.
  • ▸Each item must carry at least one of price / stopLoss / takeProfit; an omitted field is preserved at its current value, and stopLoss or takeProfit sent as 0 REMOVES that level. price is the exception — it must be positive, since a resting order needs a trigger.
  • ▸Partial success — per-order outcome in results: status "ok" (modified true) or "failed" (with a code and reason). summary tallies total / ok / failed.
GET/v1/bulk/accounts

Read live state for many accounts in one request — connection status, account financials and open positions for your whole roster (or a subset) in a single call. Requires a Pro or Enterprise plan.

Query parameters
FieldTypeDescription
idsoptionalstringComma-separated accountIds to read, e.g. "acc_8Kd3...,acc_9Lm4...". Omit to read every account on your key.
includeoptionalstringComma-separated list of what to include per account: "account", "positions". Defaults to both — e.g. include=account to skip positions and get a smaller response.
200 OK
{
  "accounts": [
    {
      "id": "acc_8Kd3...",
      "broker": "mt5",
      "server": "Exness-MT5Trial14",
      "accountNumber": "****5678",
      "externalRef": null,
      "isDemo": true,
      "hot": true,
      "poolId": "pool_3",
      "terminalType": "MOBILE",
      "alwaysHot": false,
      "lastHotAt": "2026-08-29T09:30:01.000Z",
      "createdAt": "2026-08-20T09:11:55.000Z",
      "status": "online",
      "account": { "balance": 9871.42, "currency": "USD", "equity": 9863.10, "margin": 142.50, "freeMargin": 9720.60, "marginLevel": 6921.5, "credit": null, "leverage": 500 },
      "positions": [
        { "ticket": 4072808150, "symbol": "BTCUSD", "side": "BUY", "volume": 0.10, "entryPrice": 77512.73, "currentPrice": 77640.10, "profit": 12.74 }
      ]
    },
    {
      "id": "acc_9Lm4...",
      "broker": "mt5",
      "server": "ICMarketsSC-Demo",
      "accountNumber": "****3311",
      "externalRef": null,
      "isDemo": true,
      "hot": false,
      "poolId": null,
      "terminalType": "WEB",
      "alwaysHot": false,
      "lastHotAt": "2026-08-29T08:05:44.000Z",
      "createdAt": "2026-08-21T14:02:10.000Z",
      "status": "offline"
    }
  ],
  "notFound": [],
  "summary": { "total": 2, "online": 1, "offline": 1 }
}
  • ▸Requires a Pro or Enterprise plan — on the Free and Trader plans this call returns 403.
  • ▸Not idempotent and takes no body — pass ids and include as query-string parameters.
  • ▸One round-trip for your whole roster. Each account’s status is "online" (its account and positions blocks are present, per include) or "offline" (the connection has cooled — POST /v1/sessions to re-warm it; the account/positions blocks are omitted).
  • ▸include controls the per-account payload: "account" adds the financials block, "positions" adds open positions; both are included by default. Drop one to make the response smaller.
  • ▸Any ids you pass that are not on your key come back in notFound. summary tallies total / online / offline.

Copy Trading

Mirror one master account’s trades to many follower accounts — each scaled, symbol-mapped, and risk-clamped to its own size. Create a set, tune each follower, arm it, and the moment the master trades (through TickerAll) the followers follow. All accounts are your own (self-copy).

Copy Trading requires a Pro or Enterprise plan; on the Free and Trader plans these calls return 403 (COPY_REQUIRES_PRO). Sizing, lot clamps, exposure caps, symbol allow/block lists, reverse copy, a slippage guard, and per-broker symbol overrides are all per-follower. Read a set’s /stats and /log for a full picture of what mirrored.

Once a set is armed, any trade on the master mirrors to its followers automatically — whether it was placed through this API, the dashboard, a Telegram/Discord command, a webhook, or a TradingView alert. Point a TradingView strategy (or any signal source) at your master account and the whole set follows.

POST/v1/copy/sets

Create a copy set — one master account whose trades are mirrored to your follower accounts, each scaled and risk-clamped to its own size. All accounts are your own. Requires a Pro or Enterprise plan.

Request body (JSON)
FieldTypeDescription
namereqstringA label for the set.
masterAccountIdreqstringThe account whose trades are copied. Must be one of your own accounts (and never also a follower).
failurePolicyoptionalstringWhat to do when a follower copy fails: "retry" (a few backoff attempts, the default) or "skip".
webhookUrloptionalstringOptional. A public https:// URL that receives an HMAC-signed POST for every mirror outcome (see "Copy-event delivery" below).
webhookSecretoptionalstringOptional HMAC-SHA256 signing key (16+ chars) for the X-Tickerall-Signature header. Write-only — never returned.
followersoptionalobject[]Optional followers to attach now, one object each: { followerAccountId, ...config }. Per-follower settings, all optional: sizingMethod ("proportional" — scale by the follower÷master equity ratio, the default | "multiplier" | "fixed" | "risk_percent") and sizingValue (the multiplier factor, fixed lots, or risk %); minLot / maxLot (clamp + snap the copied volume to the follower broker’s lot step); maxOpenTrades / maxExposureLots (caps); symbolAllow / symbolBlock (string arrays of follower symbols); dailyLossStop (auto-pause on loss); reverse (inverse copy — sell when the master buys); maxSlippagePips (skip if the follower price moved too far from the master fill); minMasterLot (ignore the master’s tiny trades); copyDelayMs; symbolOverrides (a { "MASTERSYMBOL": "followerSymbol" } map for cross-broker names the auto-normalizer can’t resolve).
201 Created
{
  "id": "cset_7Hb2...",
  "ownerId": "usr_9Kd3...",
  "name": "My desk",
  "masterAccountId": "acc_master",
  "enabled": false,
  "failurePolicy": "retry",
  "webhookUrl": null,
  "hasWebhookSecret": false,
  "createdAt": "2026-08-29T12:00:00.000Z",
  "updatedAt": "2026-08-29T12:00:00.000Z",
  "followers": [
    { "id": "cf_1", "followerAccountId": "acc_1", "sizingMethod": "proportional", "reverse": false, "enabled": true }
  ]
}
  • ▸Requires a Pro or Enterprise plan — on the Free and Trader plans this call returns 403 (COPY_REQUIRES_PRO).
  • ▸Self-copy only — the master and every follower must be your own accounts. A follower cannot also be the master.
  • ▸A new set is created paused (enabled false). Arm it with PATCH /v1/copy/sets/:setId { "enabled": true } once you’re happy with the followers.
  • ▸While live copying is being rolled out, mirrored trades run on demo followers; managing sets and reading stats works on all accounts.
GET/v1/copy/sets

List your copy sets, each with a follower count. Requires a Pro or Enterprise plan.

200 OK
{
  "sets": [
    { "id": "cset_7Hb2...", "name": "My desk", "masterAccountId": "acc_master", "enabled": true, "failurePolicy": "retry", "_count": { "followers": 3 }, "createdAt": "2026-08-29T12:00:00.000Z", "updatedAt": "2026-08-29T12:00:00.000Z" }
  ]
}
  • ▸Requires a Pro or Enterprise plan.
GET/v1/copy/sets/:setId

Get one copy set with its full follower list + config. Requires a Pro or Enterprise plan.

Path parameters
FieldTypeDescription
setIdreqstringThe copy set id.
200 OK
{
  "id": "cset_7Hb2...",
  "name": "My desk",
  "masterAccountId": "acc_master",
  "enabled": true,
  "failurePolicy": "retry",
  "followers": [
    { "id": "cf_1", "followerAccountId": "acc_1", "sizingMethod": "multiplier", "sizingValue": 0.5, "maxLot": 1, "reverse": false, "enabled": true }
  ]
}
  • ▸Requires a Pro or Enterprise plan.
  • ▸Returns 404 if the set is not yours.
PATCH/v1/copy/sets/:setId

Update a set — rename, change the failure policy, or arm / pause it. Requires a Pro or Enterprise plan.

Path parameters
FieldTypeDescription
setIdreqstringThe copy set id.
Request body (JSON)
FieldTypeDescription
nameoptionalstringNew label.
failurePolicyoptionalstring"retry" or "skip".
enabledoptionalbooleanArm (true) — start mirroring — or pause (false). Pausing leaves existing follower positions untouched.
webhookUrloptionalstring | nullSet the copy-event webhook (public https:// URL), or null to clear it.
webhookSecretoptionalstring | nullSet the HMAC signing key (16+ chars), or null to clear it. Write-only.
200 OK
{ "id": "cset_7Hb2...", "name": "My desk", "enabled": true, "failurePolicy": "retry", "followers": [ ... ] }
  • ▸Requires a Pro or Enterprise plan.
  • ▸Provide at least one field. Arming is just enabled: true.
DELETE/v1/copy/sets/:setId

Delete a copy set (and its followers, position map, and log). Requires a Pro or Enterprise plan.

Path parameters
FieldTypeDescription
setIdreqstringThe copy set id.
204 No Content
204 No Content — empty body.
  • ▸Requires a Pro or Enterprise plan.
  • ▸Open follower positions are NOT closed — deleting a set only stops future mirroring.
POST/v1/copy/sets/:setId/followers

Add a follower to a set. Requires a Pro or Enterprise plan.

Path parameters
FieldTypeDescription
setIdreqstringThe copy set id.
Request body (JSON)
FieldTypeDescription
followerAccountIdreqstringThe account to mirror to (one of your own; not the master, not already a follower).
...configoptionalobjectPer-follower settings, all optional: sizingMethod ("proportional" — scale by the follower÷master equity ratio, the default | "multiplier" | "fixed" | "risk_percent") and sizingValue (the multiplier factor, fixed lots, or risk %); minLot / maxLot (clamp + snap the copied volume to the follower broker’s lot step); maxOpenTrades / maxExposureLots (caps); symbolAllow / symbolBlock (string arrays of follower symbols); dailyLossStop (auto-pause on loss); reverse (inverse copy — sell when the master buys); maxSlippagePips (skip if the follower price moved too far from the master fill); minMasterLot (ignore the master’s tiny trades); copyDelayMs; symbolOverrides (a { "MASTERSYMBOL": "followerSymbol" } map for cross-broker names the auto-normalizer can’t resolve).
201 Created
{ "id": "cf_4", "copySetId": "cset_7Hb2...", "followerAccountId": "acc_4", "sizingMethod": "proportional", "reverse": true, "maxSlippagePips": 3, "enabled": true }
  • ▸Requires a Pro or Enterprise plan.
  • ▸The follower must be your own account, not the master, and not already in the set.
PATCH/v1/copy/sets/:setId/followers/:followerId

Update a follower’s config — only the fields you send change. Requires a Pro or Enterprise plan.

Path parameters
FieldTypeDescription
setIdreqstringThe copy set id.
followerIdreqstringThe follower id (cf_...), from the set’s followers list.
Request body (JSON)
FieldTypeDescription
...configoptionalobjectAny subset of Per-follower settings, all optional: sizingMethod ("proportional" — scale by the follower÷master equity ratio, the default | "multiplier" | "fixed" | "risk_percent") and sizingValue (the multiplier factor, fixed lots, or risk %); minLot / maxLot (clamp + snap the copied volume to the follower broker’s lot step); maxOpenTrades / maxExposureLots (caps); symbolAllow / symbolBlock (string arrays of follower symbols); dailyLossStop (auto-pause on loss); reverse (inverse copy — sell when the master buys); maxSlippagePips (skip if the follower price moved too far from the master fill); minMasterLot (ignore the master’s tiny trades); copyDelayMs; symbolOverrides (a { "MASTERSYMBOL": "followerSymbol" } map for cross-broker names the auto-normalizer can’t resolve). Send a field as null to clear it.
200 OK
{ "id": "cf_4", "copySetId": "cset_7Hb2...", "followerAccountId": "acc_4", "sizingMethod": "fixed", "sizingValue": 0.02, "maxLot": 1 }
  • ▸Requires a Pro or Enterprise plan.
DELETE/v1/copy/sets/:setId/followers/:followerId

Remove a follower from a set. Requires a Pro or Enterprise plan.

Path parameters
FieldTypeDescription
setIdreqstringThe copy set id.
followerIdreqstringThe follower id (cf_...).
204 No Content
204 No Content — empty body.
  • ▸Requires a Pro or Enterprise plan.
  • ▸Open positions the follower already holds are NOT closed.
GET/v1/copy/sets/:setId/stats

Dashboard stats for a set — totals, replication rate, and per-follower rollups. Requires a Pro or Enterprise plan.

Path parameters
FieldTypeDescription
setIdreqstringThe copy set id.
200 OK
{
  "set": { "id": "cset_7Hb2...", "name": "My desk", "enabled": true, "masterAccountId": "acc_master", "failurePolicy": "retry", "followerCount": 3 },
  "totals": { "ok": 128, "skipped": 4, "failed": 2, "replicationRate": 0.9846 },
  "byAction": { "open": 66, "close": 60, "modify": 8 },
  "followers": [
    { "followerAccountId": "acc_1", "enabled": true, "sizingMethod": "proportional", "ok": 44, "skipped": 1, "failed": 0, "openPositions": 2, "avgLatencyMs": 138 }
  ]
}
  • ▸Requires a Pro or Enterprise plan.
  • ▸Derived from the copy log: totals tally ok / skipped / failed; replicationRate is ok ÷ (ok + failed). Per follower: outcome counts, current open copied positions, and average fill latency.
GET/v1/copy/sets/:setId/log

The copy log for a set — every mirrored action and its outcome, newest first. Requires a Pro or Enterprise plan.

Path parameters
FieldTypeDescription
setIdreqstringThe copy set id.
Query parameters
FieldTypeDescription
limitoptionalnumberQuery param. Page size, 1–200 (default 50).
beforeoptionalstringQuery param. An ISO timestamp — return entries older than this. Use the response’s nextBefore to page.
200 OK
{
  "entries": [
    { "id": "clg_9", "followerAccountId": "acc_1", "action": "open", "masterTicket": "4072808150", "followerTicket": "5510022931", "symbol": "EURUSDm", "mappedSymbol": "EURUSD", "volume": 0.01, "result": "ok", "latencyMs": 132, "createdAt": "2026-08-29T12:34:56.000Z" },
    { "id": "clg_8", "followerAccountId": "acc_2", "action": "open", "symbol": "XAUUSDm", "volume": null, "result": "skipped", "reason": "XAUUSD is on the block list", "createdAt": "2026-08-29T12:34:56.000Z" }
  ],
  "nextBefore": "2026-08-29T12:34:56.000Z"
}
  • ▸Requires a Pro or Enterprise plan.
  • ▸Newest first. limit and before are query-string parameters; page by passing the previous response’s nextBefore back as before (null means the end).
  • ▸result is "ok", "skipped" (with a reason — e.g. filtered by a rule), or "failed" (with a reason). action is open / close / partial_close / modify / pending_place / pending_cancel / pending_modify.
Copy-event delivery (webhooks)

Set a webhookUrl on a copy set (with an optional webhookSecret) to receive a JSON POST for every mirror outcome — ok, skipped, or failed. It’s the same stream the set’s /log records, delivered as it happens. Delivery is best-effort and never blocks or delays a trade.

POST body — one copy event
{
  "event": "copy.open.ok",          // copy.<action>.<result>
  "setId": "cset_7Hb2...",
  "setName": "My desk",
  "action": "open",                  // open | close | partial_close | modify | pending_cancel | pending_modify
  "result": "ok",                    // ok | skipped | failed
  "followerAccountId": "acc_1",
  "symbol": "EURUSD",
  "mappedSymbol": "EURUSDm",         // the follower broker's symbol
  "volume": 0.25,
  "masterTicket": "123456",
  "followerTicket": "998877",
  "reason": null,                    // why, when skipped/failed
  "latencyMs": 140,
  "ts": "2026-08-29T12:00:00.000Z"
}

The event is copy.<action>.<result> (e.g. copy.close.failed) so you can route on it. When a webhookSecret is set, each request carries an X-Tickerall-Signature: sha256=<hex> header — the HMAC-SHA256 of the raw request body, keyed by your secret. Verify it before trusting the payload:

import { createHmac, timingSafeEqual } from 'crypto'

function verify(rawBody, signatureHeader, secret) {
  const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex')
  const a = Buffer.from(signatureHeader), b = Buffer.from(expected)
  return a.length === b.length && timingSafeEqual(a, b)
}

Live chat alerts, no bot setup

Discord / Telegram

Discord and Telegram channels both expose an incoming webhook URL. Point webhookUrl at one to stream mirror events straight into a chat — a zero-code way to get live copy alerts on your phone.

Webhooks & TradingView

Turn a POSTed JSON payload into a trade — from a TradingView alert, or any source that can send an HTTP POST (a script, Zapier, your own backend). Each connected account has its own incoming webhook URL, and the URL identifies the account — so no login or credentials ever go in the payload. Create one under Dashboard → TradingView, where you also get per-symbol aliases, an optional shared secret, and a dry-run test.

Webhook URL — per account, shown in the dashboard
/api/webhooks/tv/<token>     # TradingView — paste into the alert's "Webhook URL"
/api/hook/<token>            # any other source — same token, same account

Put the JSON in the TradingView alert message (or the POST body). Only action plus the fields that action needs are required:

  • action — buy · sell · close · close_all · modify
  • symbol — auto-resolved to the broker’s name (BTCUSD → BTCUSDm), case-insensitive
  • type — add "limit" / "stop" + price for a pending order
  • optional — volume (a partial close), sl, tp, ticket (target one position), comment
Payloads
# Market buy
{"action":"buy","symbol":"XAUUSD","volume":0.10}

# Pending order with SL/TP
{"action":"sell","type":"limit","symbol":"XAUUSD","volume":0.10,"price":2400,"sl":2410,"tp":2380}

# Partial close — take a chunk off (e.g. TP1/TP2/TP3; send one per level)
{"action":"close","symbol":"XAUUSD","volume":0.03}

# Close the whole position on a symbol
{"action":"close","symbol":"XAUUSD"}

# Modify stop / take-profit — breakeven = sl at entry; trail = re-send with the new sl
{"action":"modify","symbol":"XAUUSD","sl":2345.0,"tp":2380.0}

# Close every open position on the account
{"action":"close_all"}

Each webhook has an optional required secret and per-symbol aliases in its settings, plus a Test button that dry-runs a payload before you go live. Live trading follows the same plan rules as the rest of the API — see Plans & limits.

Want a full walkthrough with Pine Script templates and copy-paste alerts? See the companion guide: github.com/TickerAll/tradingview-mt5-mt4.

WebSocket — realtime data

Open a single long-lived WebSocket to wss://api.tickerall.com/v1/stream for live data. Authenticate with the same bearer token (header on the upgrade, or ?token=… in the URL). After connecting, send a subscribe message listing the channels you want.

There are four channel kinds — ticks (per-symbol price updates), positions (open/update/close events), orders (your resting pending LIMIT/STOP orders — the full book, re-sent whole on every change), and account (a full account snapshot on subscribe, then a balance update on every balance change — trade settlement, deposit, withdrawal). All are scoped by accountId.

The server also broadcasts an event frame — a service channel for maintenance notices, all-clears, news, changelog notes and announcements, sent to every connected client. You never subscribe to it; on connect the server replays the currently active notices (newest first, up to ~20) so a client joining mid-maintenance catches up. A client that doesn't handle event simply ignores it — the existing frames are unchanged.

Client → server: subscribe
send
{
  "type": "subscribe",
  "channels": [
    { "kind": "ticks", "accountId": "acc_8Kd3...", "symbols": ["BTCUSD", "ETHUSD"] },
    { "kind": "positions", "accountId": "acc_8Kd3..." },
    { "kind": "orders", "accountId": "acc_8Kd3..." },
    { "kind": "account", "accountId": "acc_8Kd3..." }
  ],
  "correlationId": "sub-1"
}

The server replies with a subscribed frame echoing which channels were accepted and which were rejected (with a code — see below). Unsubscribe with the same channel shape and "type": "unsubscribe". Send { "type": "ping" } to get a pong and keep the connection alive.

Server → client: pushes
receive
// price tick
{ "type": "tick", "accountId": "acc_8Kd3...", "symbol": "BTCUSD",
  "bid": 77512.73, "ask": 77514.10, "timestamp": "2026-05-22T18:01:22.317Z",
  "brokerTime": "2026-05-22T21:01:22.317+03:00", "stale": false }

// position lifecycle (event: "opened" | "updated" | "closed")
{ "type": "position_update", "accountId": "acc_8Kd3...", "event": "opened",
  "position": { "ticket": 4072808150, "symbol": "BTCUSD", "side": "BUY",
                "volume": 0.10, "entryPrice": 77512.73, "profit": 0 } }

// pending-order book — the FULL current list, re-sent whole on every change
{ "type": "order_update", "accountId": "acc_8Kd3...",
  "orders": [ { "ticket": "4072809988", "symbol": "BTCUSD", "type": "BUY_LIMIT",
                "side": "BUY", "orderType": "LIMIT", "volume": 0.10, "price": 68000,
                "stopLoss": 66000, "takeProfit": 72000 } ] }

// account update — pushed on every balance change (trade settlement, deposit,
// withdrawal); the snapshot sent on subscribe carries the full account
// (balance, equity, margin, freeMargin, credit, group, ...). Read /balance-operations
// to learn what moved the balance — it refreshes on the first read after the
// balance moves, so it is readable within seconds of this push.
{ "type": "account_update", "accountId": "acc_8Kd3...",
  "snapshot": { "group": "demo\\Standard", "balance": 9871.42, "currency": "USD", "credit": null } }

// service event — broadcast to every client (maintenance, news, announcements),
// replayed newest-first on connect so you catch an in-progress notice
{ "type": "event", "event": {
    "id": "evt_7Hb2...", "kind": "maintenance", "severity": "warning",
    "title": "Scheduled maintenance Sunday 06:00 UTC",
    "body": "We'll be applying updates; sessions may briefly drop and auto-reconnect.",
    "url": "https://tickerall.com/status",
    "scheduledAt": "2026-10-11T06:00:00Z", "estimatedDurationSec": 900,
    "estimatedBackAt": "2026-10-11T10:00:00Z", "reason": "weekly release window",
    "createdAt": "2026-10-09T12:00:00Z", "expiresAt": "2026-10-11T16:00:00Z" } }

// subscribe acknowledgement
{ "type": "subscribed", "channels": [ ... ], "rejected": [], "correlationId": "sub-1" }

On a tick, timestamp is the broker's own tick time in UTC (when the broker's clock offset isn't known yet, the time TickerAll received it), and brokerTime is the same instant on the broker's clock with its UTC offset, or null when unknown. stale: true marks a last-known quote — such as a closed market's last price on connect — not a live tick.

Service events (maintenance, news, announcements)

The event frame carries the notice in its event object. Only id, kind, severity, title and createdAt are always present; the rest are optional, and 0 or an empty string is a real value — test for presence, not truthiness.

  • id — stable, evt_-prefixed id for the notice (an all-clear points back to it).
  • kind — the notice type. The maintenance lifecycle uses maintenance twice (a notice authored ahead with a scheduledAt lead time, then a going-down notice broadcast as the backend restarts) and maintenance_clear when it's back; other kinds are news, changelog, announcement, or a custom string.
  • severity — info · warning · critical.
  • title · body · url — headline, longer text, and a link for more detail (e.g. the status page).
  • scheduledAt · estimatedDurationSec · estimatedBackAt · reason — when a maintenance window starts, how long it's expected to last, when service should be back, and why.
  • relatesTo — on an all-clear, the id of the maintenance event it closes.
  • createdAt · expiresAt — when the notice was issued and when it stops being replayed on connect; an absent expiresAt means no expiry.
  • data — an optional object of arbitrary extra fields for a custom event.
Subscribe rejection codes
CodeMeaning
INVALID_MESSAGEThe frame was not valid JSON or did not match the message schema.
RATE_LIMITYou sent messages too fast. Slow down and retry.
NOT_FOUNDSubscribe rejected: no account with that accountId.
FORBIDDENSubscribe rejected: that account is not yours.
BROKER_ACCOUNT_NOT_HOTSubscribe rejected: connection cooled. Call POST /v1/sessions to reconnect.
CHANNEL_LIMITSubscribe rejected: too many channels on this connection.

Protocol-level problems arrive as an error frame: { "type": "error", "code": "RATE_LIMIT", "message": "Slow down." }. The connection sends WebSocket ping frames as a heartbeat — reply with pong (most client libraries do this automatically) or you will be disconnected.

Errors

Every REST error uses the same envelope and a meaningful HTTP status. error is a stable machine code; message is human-readable. Validation failures add a details array naming the offending fields.

error envelope
{
  "error": "BROKER_REJECTED",
  "message": "Broker rejected the order: invalid volume",
  "details": [ /* present only on VALIDATION_ERROR */ ]
}
Common error codes
CodeHTTPMeaning
UNAUTHORIZED401Missing, malformed, or invalid Authorization header / API key.
VALIDATION_ERROR400The request body or params failed validation. A details array lists the offending fields.
BROKER_AUTH_FAILED401The broker rejected the login — wrong account number, password, or server. Returned by POST /v1/sessions and POST /v1/accounts/:id/reconnect. Fix the detail and retry.
BROKER_UNREACHABLE503Could not reach the broker (network/endpoint down). Retry shortly. (A wrong password no longer maps here — see BROKER_AUTH_FAILED.)
BROKER_ACCOUNT_NOT_HOT409The connection cooled. Call POST /v1/sessions with credentials to reconnect.
BROKER_REJECTED422The broker refused the order/close/modify. The reason is in message (e.g. bad volume, market closed, stop level).
TICKET_NOT_FOUND404The position ticket is not open on this account.
BROKER_ACCOUNT_NOT_FOUND404No broker account with that id belongs to your key.
BROKER_ACCOUNT_ALREADY_LINKED409That broker account is already linked to another TickerAll customer.
FREE_TIER_LIVE_REJECTED403Live account on Free outside the Terminal, or a second one at once. API live starts at Trader.
DEMO_ACCOUNT_RESERVED403That broker account is reserved by TickerAll for the public demo and cannot be attached.
TIER_ACCOUNT_CAP_REACHED403You hit your plan’s broker-account cap. Upgrade, or contact us for Enterprise.
BROKER_NOT_FOUND422The server name could not be resolved — it is not a known MT4/MT5 server. Use the exact name your terminal shows (MT5 servers are usually "Broker-MT5…"). No account row is left behind by a failed attempt. details.discovery is "not-found" when the name was checked and does not exist, or "failed" when the lookup itself failed (retry; the name may be fine). On "not-found", details.suggestions lists up to three close server names, best first (empty when none is close); they are suggestions only and are never connected automatically.
BROKER_ACCOUNT_INVALID422The broker refused the account itself (disabled, archived, expired). Fix it in your broker portal; retrying will not help.
DISCOVERY_UNAVAILABLE503Broker lookup is temporarily paused after upstream errors. Your server name was NOT rejected — retry in a few minutes. Never a verdict on the name.
SYMBOL_CATALOG_UNAVAILABLE503The account is connected but its instrument list could not be read (a TickerAll-side gap, not an empty account). GET /symbols and /symbol-specs return this instead of an empty list, so an empty array never masquerades as "no symbols". Retry; if it persists, contact support with the broker and account type.
MT4_HISTORY_UNAVAILABLE501Closed-trade history is not yet available on MT4 accounts — GET /history, /deals-history, /orders-history and /balance-operations answer with this rather than an empty list, so "no history" can never be mistaken for "no trades". Deep candle history is likewise unavailable on MT4 (the response reports coverage "partial" with complete false, never a false-empty set), and GET /symbol-specs returns an empty list while /symbols/:symbol/spec and /symbols/:symbol/quote return 404. Live prices, open positions and trading all work on MT4. For full history and symbol specs today, connect the same broker account as MT5.
ALWAYS_HOT_TIER_REQUIRED403Always-hot needs a paid plan: Trader ($0.99/mo per connection) or Pro / Enterprise (included).
VAULT_PURGE_PENDING503DELETE /v1/accounts/:id could not yet destroy the account’s remembered password, so the account was not removed. Retry shortly.
REMEMBER_PRO_REQUIRED403Remembered credentials (server-side password storage, opt-in) are available on the Pro and Enterprise plans.
rate_limited429Too many requests for this API key this minute. Budgets per key: 300/min order writes (a bulk call counts once), 600/min candles and history, 120/min POST /v1/sessions, 2000/min everything else. Wait Retry-After seconds; every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds) for its budget.
INTERNAL_ERROR500Unexpected server error. Safe to retry idempotent calls.

Plans & limits

Tiers, account caps, always-hot, and prices live on the pricing page — the single source of truth for what each plan includes and costs. This section covers only what plans mean for the API: what’s metered, and the responses that gate each tier.

What’s metered

Reads — balances, positions, ticks, candles, history — are not metered on any plan. Demo orders are never charged. What a paid plan buys is your account capacity — how many broker accounts you can connect and trade. On Trader that is live accounts (demo is free); on Pro and Enterprise, demo and live both count. A per-account cap, not a per-call quota.

Pro & Enterprise features

Bulk operations (/v1/bulk/*) and Copy Trading (/v1/copy/*) require a Pro or Enterprise plan. Choosing a non-mobile connection origin (the web or desktop terminal) is a Pro feature too.

The plan-gating responses you’ll see from the API:

  • FREE_TIER_LIVE_REJECTED — a live account on Free from the API, SDKs or MCP (Free allows one, in the Terminal).
  • 403 TIER_ACCOUNT_CAP_REACHED — you’ve hit your plan’s account cap. On Pro, demo and live accounts together count toward the 300 cap.
  • 403 COPY_REQUIRES_PRO — a Copy Trading call on a plan below Pro.
  • Bulk endpoints return 403 on the Free and Trader plans — they need Pro or Enterprise.

For the actual tiers, prices, caps, and overage, see the pricing page. Need more than Pro covers? Talk to us about Enterprise.

Ready to ship?

Grab an API key and make your first POST /v1/sessions in five minutes.

Sign up — free