Documentation

API Endpoints

Reference for the account, order, position, market data and trade history endpoints: their parameters, and what each one answers today.

📚

Base URL

Every path on this page sits under /api/v1. The platform is pre-launch and the API is not publicly hosted yet. The SDKs default to https://api.binarysword.com/api/v1, the production address the API's OpenAPI spec names, but that host does not answer yet.

Endpoint Categories

CategoryDescriptionAuth RequiredToday
AccountsBroker accounts you linkedYesThe broker accounts you linked, with their balances
OrdersOrder placement and managementYes (Trade)Market orders are sent to your broker, on a demo MetaTrader account only; other order types answer 501
PositionsOpen positions: stops, targets and closingYesRead from your broker; stops, targets and closing on a demo MetaTrader account only
Market DataInstruments, quotes, candles and ticksNoThe instrument list only: quotes, candles and ticks answer 501
Trade HistoryClosed trades, statistics and exportYesRead from your broker's deal history

Account Endpoints

Accounts are the broker accounts you linked in the app. Your tenant and user come from your API key or access token, never from a request header, and an account a colleague or another tenant linked answers 404 exactly like one that does not exist.

GET /accounts

List the broker accounts you linked, oldest first. Each account's id is the accountId that POST /orders takes, and two fields tell you whether an order will be accepted on it:

  • purpose must be trading. Introducing Broker and TradeCopier accounts never take orders.
  • isDemo must be true. A live account needs a two-factor-verified session in the app, which an API key or token cannot carry.

Balances are the last figures stored for the account, refreshed live from the broker when the account is connected. liveDataUpdatedAt is set when they were read live and null when they are the stored figures. A live read that fails keeps the stored figures and reports status: error. Responses are cached for 5 seconds.

Response

{
  "success": true,
  "data": [
    {
      "id": "6d1c8b7e-2a3f-4e5d-9c8b-7a6f5e4d3c01",
      "name": "Demo MT5",
      "broker": "Example Markets",
      "brokerType": "mt5",
      "accountNumber": "10000001",
      "server": "ExampleMarkets-Demo",
      "purpose": "trading",
      "isDemo": true,
      "currency": "USD",
      "balance": 10000.00,
      "equity": 10250.50,
      "margin": 500.00,
      "freeMargin": 9750.50,
      "marginLevel": 2050.10,
      "leverage": 100,
      "status": "connected",
      "liveDataUpdatedAt": "2026-09-26T01:30:00Z",
      "lastSyncAt": "2026-09-26T01:25:00Z",
      "createdAt": "2026-09-01T09:00:00Z",
      "updatedAt": "2026-09-26T01:25:00Z"
    }
  ]
}

Account Fields

FieldTypeDescription
idstring (UUID)Platform broker-account id; use it as the order's accountId
namestringThe account's label in the app
brokerstring | nullBroker name; null until the broker has been confirmed
brokerTypestringTrading platform, e.g. "mt5"
accountNumberstringThe broker login
purposestring"trading", "introducing_broker" or "trade_copier"
isDemobooleanTrue only for a confirmed demo account
freeMargin, marginLevel, leveragenumber | nullNull until a broker read has supplied them
statusstring"connected", "disconnected", "syncing" or "error"
liveDataUpdatedAtstring | nullWhen balances were read live; null for stored figures

GET /accounts/:id

One account, in the same shape. Answers 503 ACCOUNT_DISCONNECTED when its status is disconnected or error.

GET /accounts/:id/history

Balance snapshots recorded against this account, newest first, each with timestamp, balance, equity, margin and freeMargin. Filter with startDate and endDate (ISO 8601); interval (1h, 1d, 1w) caps the count at 168, 365 or 104 and does not aggregate. An account with no snapshots returns an empty list.


Orders Endpoints

Orders execute on a broker account you linked, classified for trading and in demo mode — never a colleague's. Look up its id with GET /accounts first.

⚠️

Current limits

Only market orders are accepted. A limit, stop or stop_limit order answers 501 ORDER_TYPE_NOT_SUPPORTED before anything is checked or recorded. Orders are placed asynchronously: POST /orders answers with status: pending, and the order moves to filled or rejected once the broker answers — poll GET /orders/:id. Order records are held by the API service and do not survive a restart.

POST /orders

Place an order. Answers 201 with the order.

Request Body

ParameterTypeRequiredDescription
accountIdstring (UUID)YesAn id from GET /accounts — never a MetaApi account id
symbolstringYesTrading instrument (e.g., "EURUSD")
typestringYesThe side: "buy" or "sell"
orderTypestringYes"market". "limit", "stop" and "stop_limit" answer 501 (see above)
volumenumberYesSize in lots, greater than 0; at most 10 per order
pricenumberNoRecorded on the order, not sent to the broker
stopLossnumberNoStop loss price
takeProfitnumberNoTake profit price
commentstringNoUp to 500 characters
clientIdstringNoYour own reference, up to 100 characters; echoed back

Request Example

POST /api/v1/orders
Content-Type: application/json

{
  "accountId": "6d1c8b7e-2a3f-4e5d-9c8b-7a6f5e4d3c01",
  "symbol": "EURUSD",
  "type": "buy",
  "orderType": "market",
  "volume": 0.1,
  "stopLoss": 1.0800,
  "takeProfit": 1.0900,
  "clientId": "my-order-001"
}

Response

HTTP/1.1 201 Created

{
  "success": true,
  "data": {
    "id": "ord_mg1x2y3z0001ab12",
    "userId": "2b7f4c1e-9d3a-4f6b-8e2c-5a1d7c9e3f04",
    "accountId": "6d1c8b7e-2a3f-4e5d-9c8b-7a6f5e4d3c01",
    "symbol": "EURUSD",
    "type": "buy",
    "orderType": "market",
    "volume": 0.1,
    "stopLoss": 1.08,
    "takeProfit": 1.09,
    "status": "pending",
    "clientId": "my-order-001",
    "createdAt": "2026-09-26T01:30:00.000Z",
    "updatedAt": "2026-09-26T01:30:00.000Z"
  }
}

Errors

StatusCodeMeaning
400VALIDATION_ERRORThe body does not match the schema above
501ORDER_TYPE_NOT_SUPPORTEDorderType is limit, stop or stop_limit; nothing is checked or recorded
404ACCOUNT_NOT_FOUNDNo account with this id that you linked (a colleague's or another tenant's answers the same way)
403ACCOUNT_PURPOSE_RESTRICTEDThe account is not classified for trading
403LIVE_ACCOUNT_RESTRICTEDA live account; this API trades demo accounts only
400UNSUPPORTED_BROKERNot a MetaTrader account
400NOT_PROVISIONEDThe account is not connected to MetaApi; reconnect it in the app
503BROKER_UNAVAILABLENo MetaApi token is configured for your tenant, or the account's broker credentials could not be read. Nothing was sent to the broker; retry
403CHALLENGE_LOCKEDThe account's newest prop-firm challenge failed, so it takes no new orders. Modify and cancel stay open
503CHALLENGE_CHECK_UNAVAILABLEThe challenge status could not be read; no order was placed
403TRADER_LIMIT_BREACHED / DESK_LIMIT_BREACHEDThe order breaks a trader or desk limit set by your head trader
409APPROVAL_REQUIREDThe order is above your approval threshold; nothing is queued
503LIMIT_CHECK_UNAVAILABLEYour limits or the account's book could not be read; no order was placed
400RISK_LIMIT_EXCEEDEDOver 10 lots, 100 orders today, or 50 orders still pending or open

GET /orders

List the orders you placed, newest first. Orders placed by another user or tenant are never listed.

Query Parameters

ParameterTypeDescription
accountIdstring (UUID)Only orders on this account
statusstringOne of pending, open, filled, partially_filled, cancelled, rejected, expired
symbolstringOnly orders on this instrument
startDate, endDatestring (ISO 8601)Only orders created within this range
pageintegerPage number (default: 1)
limitintegerOrders per page (default: 20, max: 100)

Pagination is returned in meta.pagination: page, limit, total, total_pages, has_next and has_prev.

GET /orders/:id

One order you placed. Anyone else's order answers 404.

PUT /orders/:id

Change an open order. Send at least one of price, stopLoss and takeProfit. A pending order answers 409 ORDER_IN_FLIGHT until the broker has answered, and a filled, rejected or cancelled order answers 400 ORDER_NOT_MODIFIABLE. A market order goes from pending to filled or rejected and is never open, so no order can be changed today. The stop loss of a filled order belongs to its position, which this API cannot change yet (see Positions below).

DELETE /orders/:id

Cancel an open order. A pending order answers 409 ORDER_IN_FLIGHT until the broker has answered, and any other status answers 400 ORDER_NOT_CANCELLABLE. As with changes, no order can be cancelled today: no order is ever open.


Positions Endpoints

Open positions, under /positions. Reading them needs the scope trading:read or positions:read (on an API key, Trading → Read or Positions → Read); moving a stop loss or take profit and closing need trading:write or positions:write (Trading → Write or Positions → Write). Without one, the API answers 403 INSUFFICIENT_SCOPE.

Positions are read from your broker (MetaAPI) when you ask, on the MetaTrader accounts you linked, and each account's read is cached for 2.5 seconds. A position's id is your account id, a colon and the broker's ticket, which is also sent as brokerPositionId; pass the id to the other routes as it comes. Without accountId, a list covers every account you linked except the demo paper book, and fails if any one of them cannot be read, naming it in error.details.accountId, rather than leave its positions out.

A broker that cannot be read is an error, never an empty list: 503 BROKER_UNAVAILABLE, 429 BROKER_RATE_LIMITED with Retry-After, or 400 NOT_PROVISIONED / UNSUPPORTED_BROKER for an account this API cannot reach.

GET /positions

List your open positions, oldest first.

Query Parameters

ParameterTypeDescription
accountIdstring (UUID)Only positions on this account
symbolstringOnly positions on this instrument, in capitals
pageintegerPage number (default: 1)
limitintegerPositions per page (default: 20, max: 100)

Response

unrealizedPnl is the open volume's P&L before swap and commission; realizedPnl is what partial closes realised. marginUsed is the margin your broker requires for the position's symbol, direction and volume at its open price, computed by MetaAPI, because the broker reports no margin per position; it is null when that calculation fails (your account's margin is on GET /accounts/:id). A level the position does not have is left out.

{
  "success": true,
  "data": [
    {
      "id": "6d1c8b7e-2a3f-4e5d-9c8b-7a6f5e4d3c01:46214692",
      "userId": "user-1",
      "accountId": "6d1c8b7e-2a3f-4e5d-9c8b-7a6f5e4d3c01",
      "brokerPositionId": "46214692",
      "symbol": "EURUSD",
      "type": "buy",
      "volume": 0.1,
      "entryPrice": 1.1,
      "currentPrice": 1.12,
      "takeProfit": 1.2,
      "swap": -0.5,
      "commission": -0.7,
      "unrealizedPnl": 20,
      "realizedPnl": 1,
      "marginUsed": 100,
      "openedAt": "2026-09-30T08:00:00.000Z"
    }
  ],
  "meta": {
    "timestamp": "2026-10-02T00:00:00.000Z",
    "request_id": "req_8939337a0e0927d5a8fba52a",
    "version": "1.0.0",
    "provenance": {
      "source": "metaapi",
      "accounts": ["6d1c8b7e-2a3f-4e5d-9c8b-7a6f5e4d3c01"],
      "readAt": "2026-10-02T00:00:00.000Z"
    },
    "pagination": {
      "page": 1,
      "limit": 20,
      "total": 1,
      "total_pages": 1,
      "has_next": false,
      "has_prev": false
    }
  },
  "error": null
}

GET /positions/:id

One of your open positions, in the same shape. A position that has closed answers 404 POSITION_NOT_FOUND.

⚠️

Changes reach your broker, on demo accounts only

Moving a stop loss or take profit and closing are sent to your broker. They work on a demo account classified for trading. An account classified otherwise answers 403 ACCOUNT_PURPOSE_RESTRICTED, and a live account 403 LIVE_ACCOUNT_RESTRICTED, because an API key or token cannot give the two-factor check a live change needs. Each change reads the position first. A 504 BROKER_OUTCOME_UNKNOWN means the broker did not confirm it: the change may have been made, so read the position before you try again. A repeated partial close closes more.

PUT /positions/:id/sl

Move the stop loss. Send the new price as value, a number greater than 0; anything else answers 400 VALIDATION_ERROR. It must sit below the current price on a buy and above it on a sell, or the API answers 400 INVALID_PRICE with the price in details.currentPrice. The take profit the position holds is sent with it, so it stays. The reply is the position with its new stop loss. The broker's own refusal (market closed, too close to the price) answers 422 TRADE_REJECTED.

Request

PUT /api/v1/positions/6d1c8b7e-2a3f-4e5d-9c8b-7a6f5e4d3c01:46214692/sl
Content-Type: application/json

{
  "value": 1.105
}

PUT /positions/:id/tp

Move the take profit, with the same value body: above the current price on a buy, below it on a sell.

POST /positions/:id/close

Close a position at market. Send volume in lots to close part of it, or {} to close all of it. Send a JSON body either way: a request with no body answers 400 VALIDATION_ERROR. A volume above the position's answers 400 INVALID_VOLUME.

Request

POST /api/v1/positions/6d1c8b7e-2a3f-4e5d-9c8b-7a6f5e4d3c01:46214692/close
Content-Type: application/json

{
  "volume": 0.04
}

Response

closePrice, realizedPnl (the closing deal's profit, before commission and swap) and closedAt come from the closing deal when the API finds it straight after the close, and are null when it does not; GET /trades shows the fill later.

{
  "success": true,
  "data": {
    "id": "6d1c8b7e-2a3f-4e5d-9c8b-7a6f5e4d3c01:46214692",
    "accountId": "6d1c8b7e-2a3f-4e5d-9c8b-7a6f5e4d3c01",
    "brokerPositionId": "46214692",
    "brokerOrderId": "ord-9",
    "closedVolume": 0.04,
    "remainingVolume": 0.06,
    "closePrice": null,
    "realizedPnl": null,
    "closedAt": null
  },
  "meta": {
    "timestamp": "2026-10-02T00:00:00.000Z",
    "request_id": "req_8939337a0e0927d5a8fba52a",
    "version": "1.0.0"
  },
  "error": null
}

Market Data Endpoints

These four routes sit directly under the base URL, with no /market segment, and need no API key. Write symbols in capitals: eurusd answers 400 VALIDATION_ERROR, and a symbol the API does not list answers 404 NOT_FOUND.

⚠️

No prices yet

The quote, candle and tick endpoints are not connected to a market data feed yet, so they serve no prices. For a listed symbol and a valid request they answer 501 NOT_IMPLEMENTED with data: null. The instrument list, GET /symbols, works.

GET /symbols

The 15 instruments the API lists: forex pairs, metals, oil, indices and one stock. The list is fixed in the API, not read from your broker. Responses are cached for 60 seconds.

Query Parameters

ParameterTypeDescription
typestringforex, metal, commodity, index or stock
searchstringText to find in the symbol or its description, up to 50 characters

Response

GET /api/v1/symbols?search=eur

{
  "success": true,
  "data": [
    {
      "symbol": "EURUSD",
      "description": "Euro vs US Dollar",
      "type": "forex",
      "tickSize": 0.00001,
      "contractSize": 100000,
      "marginRate": 0.01,
      "tradingHours": "Sun 22:00 - Fri 22:00 UTC",
      "currency": "USD",
      "baseCurrency": "EUR",
      "quoteCurrency": "USD"
    }
  ],
  "meta": {
    "timestamp": "2026-10-01T16:41:07.611Z",
    "request_id": "req_eb3df6eda33f8259e82250a1",
    "version": "1.0.0"
  },
  "error": null
}

Symbol Fields

FieldTypeDescription
tickSizenumberSmallest price step
contractSizenumberUnits in one lot
marginRatenumberMargin as a fraction of the position's value (0.01 is 1:100). A placeholder, not your broker's rate
tradingHoursstringTrading hours as text
currencystringThe currency prices are quoted in
baseCurrency, quoteCurrencystringForex pairs only

GET /quotes/:symbol

The current bid and ask for one instrument.

GET /candles/:symbol

OHLCV candles for one instrument.

Query Parameters

ParameterTypeDescription
timeframestringRequired. 1m, 5m, 15m, 30m, 1h, 4h, 1d, 1w or 1M
startTime, endTimestring (ISO 8601)The time range
limitintegerMax candles (default: 100, max: 1000)

GET /ticks/:symbol

Bid and ask ticks for one instrument. Takes startTime and endTime (ISO 8601) and limit (default: 1000, max: 10000).

Response Today

A valid quote, candle or tick request for a listed symbol answers:

HTTP/1.1 501 Not Implemented

{
  "success": false,
  "data": null,
  "error": {
    "code": "NOT_IMPLEMENTED",
    "message": "Market prices are not connected to a data feed on this API yet, so it serves no quotes, candles or ticks.",
    "details": {
      "feature": "market-data"
    },
    "request_id": "req_61bef08d345dd9c671fd6c79"
  }
}

The API has no order book (market depth) endpoint.


Trade History Endpoints

Closed trades, under /trades. Every route needs the scope trading:read or history:read (on an API key, Trading → Read or History → Read).

A trade is one broker position whose volume has all been closed, folded from every deal on it and read from your broker (MetaAPI) when you ask: volume-weighted entry and exit prices, and profit, commission and swap summed across its deals. netProfit is profit + commission + swap (a charge is negative). A position still partly open is not a trade yet. A trade's id is your account id, a colon and the broker's position id. Each account's deal history is cached for 5 seconds. Accounts are covered, and a broker that cannot be read is answered, as for positions; a history longer than one request reads (20,000 deals) answers 422 HISTORY_TOO_LARGE rather than part of it.

GET /trades

List your closed trades, the most recently closed first.

Query Parameters

ParameterTypeDescription
accountIdstring (UUID)Only trades on this account
symbolstringOnly trades on this instrument, in capitals
startDate, endDatestring (ISO 8601)Only trades closed in this range, both ends included. A trade opened before startDate counts if it closed after it. A startDate after endDate answers 400 VALIDATION_ERROR
profitFilterstringprofit (net profit above 0), loss (0 or below) or all (default)
limitintegerTrades per page (default: 20, max: 100)
cursorstringmeta.pagination.cursor from the previous page; it is absent on the last page. A cursor the API cannot read answers 400 INVALID_CURSOR

Response

{
  "success": true,
  "data": [
    {
      "id": "6d1c8b7e-2a3f-4e5d-9c8b-7a6f5e4d3c01:p2",
      "userId": "user-1",
      "accountId": "6d1c8b7e-2a3f-4e5d-9c8b-7a6f5e4d3c01",
      "positionId": "p2",
      "symbol": "EURUSD",
      "type": "buy",
      "volume": 0.1,
      "entryPrice": 1.11,
      "exitPrice": 1.108,
      "swap": -1,
      "commission": 0,
      "profit": -20,
      "netProfit": -21,
      "holdingTime": 86400,
      "openedAt": "2026-09-03T10:00:00.000Z",
      "closedAt": "2026-09-04T10:00:00.000Z"
    }
  ],
  "meta": {
    "timestamp": "2026-10-02T00:00:00.000Z",
    "request_id": "req_8939337a0e0927d5a8fba52a",
    "version": "1.0.0",
    "provenance": {
      "source": "metaapi",
      "accounts": ["6d1c8b7e-2a3f-4e5d-9c8b-7a6f5e4d3c01"],
      "readAt": "2026-10-02T00:00:00.000Z",
      "skipped": []
    },
    "pagination": {
      "page": 1,
      "limit": 1,
      "total": 2,
      "total_pages": 2,
      "has_next": true,
      "has_prev": false,
      "cursor": "eyJ0IjoxNzg4NTE2MDAwMDAwLCJpIjoiNmQxYzhiN2UtMmEzZi00ZTVkLTljOGItN2E2ZjVlNGQzYzAxOnAyIn0"
    }
  },
  "error": null
}

meta.provenance.skipped lists any position whose deals could not be folded into a trade, with the reason: unsupported_deal_entry_type (a reversal or close-by deal), no_open_deal, zero_open_volume, closed_exceeds_opened, invalid_timestamp, missing_symbol_or_times or missing_price. A skipped position is in no list and no statistic. volumeEstimated: true appears on a trade when one of its closing deals reported no volume; its volume is then the volume opened.

GET /trades/stats

Totals over your closed trades' net profit, filtered by accountId, symbol, startDate and endDate. A trade at 0 or below counts as a loss. sharpeRatio is the mean trade over its standard deviation, per trade and not annualised. confidenceIntervals gives 95% intervals for the win rate (Wilson; null with no trades) and the mean trade (Student-t; null below two trades). With two trades the mean-trade interval is very wide, as below. The figures come in the reply's data, with meta.provenance as for GET /trades:

{
  "totalTrades": 2,
  "winningTrades": 1,
  "losingTrades": 1,
  "winRate": 50,
  "profitFactor": 2.33,
  "averageWin": 49,
  "averageLoss": 21,
  "largestWin": 49,
  "largestLoss": 21,
  "totalProfit": 49,
  "totalLoss": 21,
  "netProfit": 28,
  "sharpeRatio": 0.4,
  "maxDrawdown": 21,
  "confidenceIntervals": {
    "level": 0.95,
    "winRate": { "lower": 9.45, "upper": 90.55 },
    "averageTrade": { "lower": -430.71, "upper": 458.71 }
  }
}

GET /trades/export

Your closed trades as a file, with the filters of GET /trades except cursor and limit. format is csv (default) or json. The response is the file itself, sent as an attachment named trades_YYYY-MM-DD.csv (or .json); there is no download link.

GET /trades/:id

One of your closed trades, in the shape above. A position still open answers 404 TRADE_NOT_FOUND.

The API has no endpoint for deposits and withdrawals.


HTTP Status Codes

CodeMeaning
200Success
201Created (new resource)
400Bad Request (invalid parameters or JSON, or a request the account cannot take, such as a stop on the wrong side of the price)
401Unauthorised (no valid API key or token, or credentials sent in the body or query string instead of a header)
403Forbidden (the key or token lacks the scope, or the account or a limit refuses the request)
404Not Found
409Conflict (the order is still being placed, or needs approval)
413Payload Too Large (a request body over 10 MB)
422Unprocessable (the broker refused a change, or a trade history is too long to read whole)
429Too Many Requests (this API's rate limit, see API Quick Start, or the broker's on your account; wait for Retry-After)
500Server Error
501Not Implemented (quotes, candles and ticks, and order types the API cannot place yet)
503Service Unavailable (the broker, the database or a check could not be read)
504Gateway Timeout (the broker did not confirm a position change, so read the position before retrying; or a call inside the API timed out)

Next Steps

⚠️

Risk Disclosure

Trading via API carries the same risks as manual trading. There is no separate sandbox: the API places orders on your linked demo accounts only. Never trade money you cannot afford to lose.