Skip to content
API status
Automatic trading

Trader API integrations

Plan safe copier, market data, and trading integrations with the Koneth Trader API.

The Koneth Trader API connects an external automation service to one trading account on one Koneth trading server. It is separate from the Management API used by organizations.

Each broker or organization runs its own trading servers, and each server has its own API URL. Find the one that holds the account you want to automate:

  1. Ask your broker or organization. They own the server and can give you its exact API URL. This is the only reliable answer when they host the server on their own domain.
  2. Try the Koneth server hostname. Servers hosted by Koneth are normally reachable at https://<server-name-lowercased>.server.koneth.com. Take the server name exactly as Terminal shows it, lowercase it, and keep the punctuation — AcmeMarkets-Live30 becomes https://acmemarkets-live30.server.koneth.com.
  3. Search server discovery. This public endpoint needs no key, accepts an optional name filter, and returns at most 50 active servers. Read apiBaseUrl from the entry that matches.
Terminal window
curl "https://api.koneth.com/api/v1/terminal/servers?name=AcmeMarkets"
{
"servers": [
{
"id": "6be9a699-2f28-402e-b64e-93ba6bdafe1b",
"name": "AcmeMarkets-Live30",
"environment": "live",
"apiBaseUrl": "https://acmemarkets-live30.server.koneth.com",
"defaultInstruments": ["XAUUSD", "EURUSD", "BTCUSD"],
"organizationName": "Acme Markets",
"organizationLogoUrl": "https://cdn.example.com/logo.png"
}
]
}

Ask your broker or organization for the API URL if discovery does not list their server. A server they host on their own domain does not appear under server.koneth.com.

GET {API URL}/api/v1/server/info needs no session and returns the server name, currency, and environment:

Terminal window
curl "https://acmemarkets-live30.server.koneth.com/api/v1/server/info"
{
"serverId": "6be9a699-2f28-402e-b64e-93ba6bdafe1b",
"serverName": "AcmeMarkets-Live30",
"companyName": "Acme Markets",
"currency": "USD",
"currencySymbol": "$",
"environment": "live",
"supportedLeverage": [100, 200, 500],
"maxLeverage": 500,
"unlimitedMarginCallPercent": 50,
"unlimitedStopOutPercent": 20,
"defaultLeverage": 100,
"marginCallLevel": 100,
"stopOutLevel": 50,
"status": "active"
}

If the name that comes back is not the server you meant, stop and ask your broker or organization for the correct URL. A wrong host cannot authenticate your account, and guessing wastes login attempts against the login rate limit.

A session belongs to one server. An account on another server needs its own login and its own token.

Every path below is relative to your server’s API URL. The Success column is the status code a request that worked returns — 201 means a record was created, and 202 means it was accepted for later execution. The Investor password and Trading password columns show which session can call the endpoint; a dash means the endpoint needs no session. An investor session returns TRADING_ACCESS_REQUIRED on a route that needs the trading password.

Session

Endpoint Purpose Success Investor password Trading password
POST /v1/auth/login Create an account-scoped session 200 Yes Yes
POST /api/v1/auth/login The same login with the Terminal’s response shape 200 Yes Yes
POST /api/v1/auth/resume Resume a session from a remembered token 200 Yes Yes
POST /api/v1/auth/logout End the current session 200 Yes Yes
GET /api/v1/server/info Server name, currency, environment, and leverage limits 200 — —

Market data

Endpoint Purpose Success Investor password Trading password
GET /api/v1/markets List the instruments this account can trade 200 Yes Yes
GET /api/v1/markets/{symbol} Read one instrument’s terms 200 Yes Yes
GET /api/v1/markets/{symbol}/quote Read the current bid and ask 200 Yes Yes
GET /api/v1/markets/{symbol}/candles Read historical candles 200 Yes Yes
POST /api/v1/account/market-token Exchange the session for a short-lived market data token 200 Yes Yes

Trading

Endpoint Purpose Success Investor password Trading password
POST /v1/trade Open a market position 201 No Yes
POST /api/v1/orders Open a market, limit, or stop order 201 filled, 202 resting No Yes
GET /api/v1/orders List pending orders 200 Yes Yes
POST /api/v1/orders/{orderId}/cancel Cancel a pending order 200 No Yes
POST /api/v1/orders/{orderId}/modify Re-price a pending order 200 No Yes
POST /api/v1/positions/{positionId}/modify Change stop loss or take profit 200 No Yes
POST /api/v1/positions/{positionId}/close Close one position, fully or partly 200 No Yes
POST /api/v1/positions/close Close every position, or every position on one side 200 No Yes

Account, history, and streams

Endpoint Purpose Success Investor password Trading password
GET /v1/account Read balance, equity, margin, and account details 200 Yes Yes
GET /api/v1/account The same snapshot with the Terminal’s field names 200 Yes Yes
GET /api/v1/account/balance Read balance and currency 200 Yes Yes
GET /api/v1/account/transactions Read deposits, withdrawals, and balance changes 200 Yes Yes
GET /api/v1/positions Read open positions and current valuation 200 Yes Yes
GET /api/v1/trading/history Read orders, positions, and deals 200 Yes Yes
GET /api/v1/trading/history/summary Read the totals for one period 200 Yes Yes
GET /api/v1/trading/history/chart Read the deals behind a chart window 200 Yes Yes
GET /v1/events Replay execution events after a cursor 200 Yes Yes
GET /api/v1/account/events The same replay on the /api/v1 path 200 Yes Yes
GET /v1/stream Open a replayable position-event WebSocket 101 upgrade Yes Yes
WS /api/v1/account/stream The same stream on the /api/v1 path 101 upgrade Yes Yes
Terminal window
curl --request POST "https://YOUR-SERVER.example.com/v1/auth/login" \
--header "Content-Type: application/json" \
--data '{
"account": "10002341",
"password": "YOUR_TRADING_OR_INVESTOR_PASSWORD"
}'
{
"access_token": "SESSION_TOKEN",
"token_type": "Bearer",
"expires_in": 604800,
"access_mode": "trade",
"account": "10002341",
"server": "Koneth Demo"
}

Send the session token in the Authorization header:

Authorization: Bearer SESSION_TOKEN

An investor password creates read_only access. The server returns TRADING_ACCESS_REQUIRED if that session calls a trading endpoint.

Terminal window
curl "https://YOUR-SERVER.example.com/v1/account" \
--header "Authorization: Bearer SESSION_TOKEN"
{
"account": "10002341",
"name": "Copier Demo",
"balance": 10000,
"equity": 10125.42,
"margin": 350.1,
"free_margin": 9775.32,
"margin_level": 2892.15,
"floating_profit": 125.42,
"currency": "USD",
"leverage": 100,
"unlimitedLeverage": false,
"environment": "demo",
"access_mode": "trade"
}

Account values are refreshed from authoritative prices before the response.

Market data is account-scoped: a session can only read the instruments attached to the server that hosts the account. Start by listing them.

Terminal window
curl "https://YOUR-SERVER.example.com/api/v1/markets" \
--header "Authorization: Bearer SESSION_TOKEN"
{
"markets": [
{
"symbol": "XAUUSD",
"name": "Gold vs US Dollar",
"category": "metals",
"status": "active",
"precision": 2,
"tickSize": 0.01,
"profitCurrency": "USD",
"contractSize": 100,
"spreadMode": "floating",
"spreadPoints": 25,
"minSpreadPoints": 10,
"commissionPerLot": 0,
"swapLongPoints": -12.5,
"swapShortPoints": 4.2,
"minLot": 0.01,
"maxLot": 50,
"lotStep": 0.01,
"marginRate": 0.01,
"maxLeverage": 500,
"marketOpen": true
}
]
}

GET /api/v1/markets/{symbol} returns one instrument with the same fields, without the markets wrapper. A symbol the server does not carry answers 404 with {"error": "Instrument not available"}, which is also what you get for a symbol on another server. maxLeverage: null means the instrument has no leverage ceiling. Check marketOpen before you expect a market order to fill, and read minLot, maxLot, and lotStep before you size a position.

Terminal window
curl "https://YOUR-SERVER.example.com/api/v1/markets/XAUUSD/quote" \
--header "Authorization: Bearer SESSION_TOKEN"
{
"tick": {
"symbol": "XAUUSD",
"bid": 3341.65,
"ask": 3341.8,
"timestamp": 1787669462442,
"sequence": "1842",
"source": "oanda"
}
}

The quote already has your server’s spread applied, so the price you read here is the price your market orders execute against. timestamp is Unix epoch milliseconds; source names the provider that produced the price. The server keeps a short-lived quote cache — by default a tenth of a second — so this route is a snapshot, not a tick-by-tick feed. Use the market data WebSocket below when you need every update.

/quote and /candles answer 503 with MARKET_UNAVAILABLE when the market data service cannot answer — a closed market, a provider outage, or a market token that no longer matches the server’s published configuration. That is an availability error rather than a rejected symbol: back off and retry instead of treating the instrument as untradeable.

Terminal window
curl "https://YOUR-SERVER.example.com/api/v1/markets/XAUUSD/candles?timeframe=15m&limit=500" \
--header "Authorization: Bearer SESSION_TOKEN"
{
"symbol": "XAUUSD",
"timeframe": "15m",
"candles": [
{
"time": 1787668800000,
"open": 3340.2,
"high": 3342.1,
"low": 3338.9,
"close": 3341.7,
"volume": 1240
}
],
"history": {
"storedTimeframe": "M5",
"hasCompleteCoverage": true,
"ranges": [
{
"requestedFrom": "2026-06-18T00:00:00.000Z",
"requestedTo": "2026-09-16T00:00:00.000Z",
"availableFrom": "2026-06-18T00:00:00.000Z",
"availableThrough": "2026-09-16T00:00:00.000Z",
"status": "complete",
"lastError": null
}
]
}
}
Query parameter Type Default Notes
timeframe string Required 1m, 5m, 15m, 30m, 1h, 4h, or 1d
limit number 500 1 to 1000 candles
to number — Upper bound in Unix epoch milliseconds. Returns candles whose open time is before it
from number — Lower bound in Unix epoch milliseconds. Applied after the candles are read, so it can shorten the page

time is Unix epoch milliseconds, not seconds, and candles are returned oldest first. To page further back, send the time of the oldest candle you received as to.

Koneth stores M1, M5, H1, and D1 candles, so 15m and 30m are aggregated from stored M5 candles and 4h from stored H1 candles. The history block tells you which stored timeframe answered the request, whether coverage is complete, and which ranges exist. An incomplete range means the chart can show a gap; it does not mean the price is wrong.

The /quote and /candles routes above cover most integrations. For a live price stream, or for the raw provider form of a quote, exchange the session token for a short-lived market token:

Terminal window
curl --request POST "https://YOUR-SERVER.example.com/api/v1/account/market-token" \
--header "Authorization: Bearer SESSION_TOKEN"
{
"token": "MARKET_TOKEN",
"expiresInSeconds": 300,
"marketDataBaseUrl": "https://market.example.com/v1",
"configurationRevision": "41f1c2a70b8e"
}

Send that token as the bearer to marketDataBaseUrl:

Endpoint Purpose
GET {marketDataBaseUrl}/quotes/{symbol} Quote in provider form: bid and ask as decimal strings, plus providerId, providerSymbol, providerTimestamp, sequence, and feed state
GET {marketDataBaseUrl}/candles/{symbol} Raw candles: timeframe is M1, M5, M15, M30, H1, H4, or D1, limit up to 5000, and an optional before timestamp
GET {marketDataBaseUrl}/reference-rates/{symbol} Published conversion rate such as USDNGN, used for account currency conversion
wss://{host}/v1/stream/{symbol}?token=MARKET_TOKEN Live quote and feed-status frames for one symbol

The market token expires after 300 seconds. Request a fresh one before it expires and expect a rejected connection once your server’s market configuration is republished. The token is bound to one server, one account, and the symbols that account may read, so it is not a replacement for a Koneth server login.

Terminal window
curl "https://YOUR-SERVER.example.com/api/v1/positions" \
--header "Authorization: Bearer SESSION_TOKEN"
{
"positions": [
{
"id": "c4cd8f3e-96df-4c15-aa08-6e2a8e43ee92",
"orderId": "5c675e4d-35c1-411b-acd5-5391865d344b",
"symbol": "XAUUSD",
"side": "BUY",
"volume": 0.1,
"openPrice": 3341.2,
"currentPrice": 3346.8,
"stopLoss": 3300,
"takeProfit": 3400,
"status": "OPEN",
"floatingPnl": 56,
"usedMargin": 334.12,
"openedAt": "2026-08-25T12:10:30.120Z",
"closedAt": null,
"providerTimestamp": 1787669462442,
"sequence": "1842",
"source": "market-provider"
}
]
}

OPEN submits a market order. The server executes against its authoritative quote, not a client-supplied price.

Terminal window
curl --request POST "https://YOUR-SERVER.example.com/v1/trade" \
--header "Authorization: Bearer SESSION_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"action": "OPEN",
"client_request_id": "copier-20260825-000001",
"symbol": "XAUUSD",
"side": "BUY",
"volume": 0.10,
"stop_loss": 3300,
"take_profit": 3400
}'
{
"action": "OPEN",
"client_request_id": "copier-20260825-000001",
"order_id": "5c675e4d-35c1-411b-acd5-5391865d344b",
"position_id": "c4cd8f3e-96df-4c15-aa08-6e2a8e43ee92",
"status": "FILLED",
"execution_price": 3341.2,
"executed_at": "2026-08-25T12:10:30.120Z"
}

The server answers 201 with the filled order — a market open is a creation, so it is not a 200. client_request_id must be unique for each intended open action. Retrying the same request with the same identifier returns the original order. Reusing it with different order parameters returns 409 with CLIENT_ORDER_ID_REUSED.

Send an absolute stop-loss value, take-profit value, or both. Send null to remove a level.

Terminal window
curl --request POST \
"https://YOUR-SERVER.example.com/api/v1/positions/c4cd8f3e-96df-4c15-aa08-6e2a8e43ee92/modify" \
--header "Authorization: Bearer SESSION_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"stopLoss": 3320,
"takeProfit": 3410
}'

Repeating the same absolute modification is safe. The server returns the current protection values without creating another position event when nothing changed. It rejects modifications to a closed position.

Terminal window
curl --request POST \
"https://YOUR-SERVER.example.com/api/v1/positions/c4cd8f3e-96df-4c15-aa08-6e2a8e43ee92/close" \
--header "Authorization: Bearer SESSION_TOKEN" \
--header "Content-Type: application/json" \
--data '{}'

An empty body closes the full position at the server’s authoritative closing price. To close only part of it, send a positive volume and a unique Idempotency-Key header:

Terminal window
curl --request POST \
"https://YOUR-SERVER.example.com/api/v1/positions/c4cd8f3e-96df-4c15-aa08-6e2a8e43ee92/close" \
--header "Authorization: Bearer SESSION_TOKEN" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: copier-close-20260825-000001" \
--data '{"volume": 0.05}'
Terminal window
curl --request POST "https://YOUR-SERVER.example.com/api/v1/positions/close" \
--header "Authorization: Bearer SESSION_TOKEN" \
--header "Content-Type: application/json" \
--data '{"side": "ALL"}'

side accepts ALL, BUY, or SELL and defaults to ALL. The response lists the positions that closed:

{
"closed": [
{
"positionId": "c4cd8f3e-96df-4c15-aa08-6e2a8e43ee92",
"symbol": "XAUUSD",
"side": "BUY",
"closedVolume": 0.1,
"remainingVolume": 0,
"status": "CLOSED",
"executionPrice": 3346.8,
"realizedPnl": 56,
"commission": 0,
"reason": "MANUAL",
"closedAt": "2026-08-25T14:02:11.004Z"
}
]
}

Positions close one at a time, so read the returned list instead of assuming every position closed. Compare its length with GET /api/v1/positions before you report success.

POST /api/v1/orders accepts MARKET, LIMIT, and STOP. A LIMIT or STOP order needs requestedPrice and rests until the market reaches it.

Terminal window
curl --request POST "https://YOUR-SERVER.example.com/api/v1/orders" \
--header "Authorization: Bearer SESSION_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"clientOrderId": "copier-xauusd-000042",
"symbol": "XAUUSD",
"side": "BUY",
"volume": 0.10,
"orderType": "LIMIT",
"requestedPrice": 3320.00,
"stopLoss": 3300,
"takeProfit": 3400
}'

A market order answers 201 with a filled order. A pending order answers 202:

{
"orderId": "6c0d3a2d-3b3d-4d1f-9c05-3de0b0f3f9a1",
"clientOrderId": "copier-xauusd-000042",
"status": "PENDING",
"requestedPrice": 3320,
"acceptedAt": "2026-08-25T12:10:30.120Z",
"executionVersion": 3
}

clientOrderId must be 8 to 100 characters and unique per intended order. Reusing it on a retry returns the original order; reusing it with different details returns 409 with CLIENT_ORDER_ID_REUSED.

Terminal window
curl "https://YOUR-SERVER.example.com/api/v1/orders?page=1&limit=50" \
--header "Authorization: Bearer SESSION_TOKEN"
Terminal window
curl --request POST \
"https://YOUR-SERVER.example.com/api/v1/orders/6c0d3a2d-3b3d-4d1f-9c05-3de0b0f3f9a1/modify" \
--header "Authorization: Bearer SESSION_TOKEN" \
--header "Idempotency-Key: copier-modify-20260825-000001" \
--header "Content-Type: application/json" \
--data '{"requestedPrice": 3315.00, "stopLoss": 3295, "takeProfit": null}'

Cancel with POST /api/v1/orders/{orderId}/cancel and the same headers. Both cancel and modify accept an optional Idempotency-Key: reusing a key with the same details replays the original result instead of acting twice, and reusing it with different details returns ACTION_ID_REUSED. Only pending orders can be cancelled or modified; a filled or already-cancelled order returns a conflict.

Terminal window
curl "https://YOUR-SERVER.example.com/api/v1/trading/history?page=1&limit=50" \
--header "Authorization: Bearer SESSION_TOKEN"

The response carries orders, positions, and deals, plus pagination with page, limit, and hasMore. Each deal includes symbol, side, kind, volume, price, commission, realizedPnl, swap, closeReason, and executedAt:

{
"deals": [
{
"id": "8f2b1c44-6d3e-4a71-9f0c-2b6d5e8a1c93",
"orderId": "5c675e4d-35c1-411b-acd5-5391865d344b",
"positionId": "c4cd8f3e-96df-4c15-aa08-6e2a8e43ee92",
"symbol": "XAUUSD",
"side": "BUY",
"kind": "ENTRY",
"volume": 0.1,
"price": 3341.2,
"commission": 0,
"realizedPnl": 0,
"swap": -0.42,
"closeReason": null,
"executedAt": "2026-08-25T12:10:30.120Z"
}
],
"pagination": { "page": 1, "limit": 50, "hasMore": false }
}

from, to, and symbol narrow the list to the period and market a screen is showing. ISO-8601 from and to are inclusive, and symbol is the instrument as the server spells it:

Terminal window
curl "https://YOUR-SERVER.example.com/api/v1/trading/history?from=2026-06-16T00:00:00.000Z&to=2026-09-17T00:00:00.000Z&symbol=XAUUSD&page=1&limit=100" \
--header "Authorization: Bearer SESSION_TOKEN"

GET /api/v1/trading/history/summary takes the same from, to, and symbol and returns what that period contributed, plus the balance it left behind:

Terminal window
curl "https://YOUR-SERVER.example.com/api/v1/trading/history/summary?from=2026-06-16T00:00:00.000Z&to=2026-09-17T00:00:00.000Z" \
--header "Authorization: Bearer SESSION_TOKEN"
{
"summary": {
"from": "2026-06-16T00:00:00.000Z",
"to": "2026-09-17T00:00:00.000Z",
"symbol": null,
"deposit": 500,
"withdrawal": 0,
"profit": -24.39,
"swap": 0,
"commission": 0,
"closedVolume": 0.45,
"deals": 90,
"balance": 475.61
}
}

These totals are aggregated over the whole window, not over one page of deals, so they are what a statement can be reconciled against: over a window that covers the account’s whole life, deposit + withdrawal + profit - commission + swap equals balance. commission is the cost the deals recorded, which is the figure a statement prints as a deduction, so subtract it rather than adding it. Balance operations carry no symbol, so deposit and withdrawal ignore the symbol filter.

GET /api/v1/trading/history/chart returns the deals for one symbol and time window, used by chart overlays. It requires symbol plus ISO-8601 from and to values, and accepts limit up to 2000. It answers { "deals": [ ... ] } without commission, swap, or realized profit.

Terminal window
curl "https://YOUR-SERVER.example.com/api/v1/account/balance" \
--header "Authorization: Bearer SESSION_TOKEN"
{
"accountNumber": "10002341",
"balance": 10000,
"currency": "USD"
}

GET /api/v1/account/transactions returns deposits, withdrawals, and other balance changes with pagination containing total and totalPages. Use GET /v1/account when you need equity and margin, and this route when you only need settled balance.

Endpoint Purpose
POST /api/v1/auth/login Sign in and receive token, rememberedToken, and the account
POST /api/v1/auth/resume Exchange accountNumber and rememberedToken for a new session
POST /api/v1/auth/logout Invalidate the current session
GET /api/v1/server/info Server name, currency, environment, and leverage limits. Needs no session
Terminal window
curl --request POST "https://YOUR-SERVER.example.com/api/v1/auth/resume" \
--header "Content-Type: application/json" \
--data '{
"accountNumber": "10002341",
"rememberedToken": "REMEMBERED_TOKEN"
}'

A remembered token is scoped to one account on one server. Since it can be exchanged for a session without a password, store it with the same care as the session token, and call logout when an automation is retired.

Open a WebSocket connection to /v1/stream with the bearer token in the handshake’s Authorization header. Add the last processed cursor when reconnecting:

wss://YOUR-SERVER.example.com/v1/stream?cursor=1842

The same stream is also reachable at wss://YOUR-SERVER.example.com/api/v1/account/stream?cursor=1842, and the same events are available over HTTP from GET /v1/events or GET /api/v1/account/events when a WebSocket does not fit your runtime.

The first frame confirms the connection and contains any missed events after that cursor:

{
"type": "READY",
"cursor": 1844,
"events": [
{
"eventId": "b4ad9d81-2c26-41c0-9200-004e10b608da",
"cursor": 1843,
"type": "ORDER_FILLED",
"orderId": "5c675e4d-35c1-411b-acd5-5391865d344b",
"positionId": null,
"payload": {
"symbol": "XAUUSD",
"side": "BUY",
"volume": 0.1,
"orderType": "MARKET",
"executionPrice": 3341.2
},
"occurredAt": "2026-08-25T12:10:30.120Z"
}
],
"replayTruncated": false
}

Live frames use type: "EVENTS". The event types include POSITION_OPENED, POSITION_MODIFIED, POSITION_REDUCED, POSITION_CLOSED, order lifecycle events, and SWAP_ACCRUED.

Store the highest cursor only after your application has durably processed every earlier event. If replayTruncated is true, reconcile with GET /api/v1/positions before continuing. Send the text frame ping to receive a PONG response.

Browser WebSocket clients cannot add an Authorization header. Connect from a trusted backend or native client instead of exposing the session token in browser code.

Every server API call is rate limited, and some limits are tighter than others. A request is counted against each limit it falls under, so a trade spends one of your trading calls and one of your general API calls in the same minute.

Bucket Endpoints Limit
Authentication POST /api/v1/auth/*, POST /v1/auth/* 10 per minute
Trading /v1/trade, /api/v1/orders, /api/v1/orders/*, /api/v1/positions, /api/v1/positions/* 120 per minute
Server API everything else under /api/* and /v1/*, including market data, history, and account reads 1200 per minute
Contact form POST /api/v1/contact 5 per minute
Server setup /api/v1/setup/* 20 per minute

A bucket counts one identity: the session token on the request (Authorization: Bearer …) or, for a request without one, the caller’s IP address. Two tokens have two budgets, and one token reused by every task in an integration shares a single budget with all of them. Counters live in the server process, so a server restart clears them.

Every response from a limited route carries the counters:

Header Meaning
RateLimit-Limit The ceiling of that bucket
RateLimit-Remaining Requests left in the current window
RateLimit-Reset Unix epoch seconds when the window resets

Exceeding a bucket answers 429 with Retry-After in seconds:

{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests"
}
}

The login bucket is the one integrations hit first: ten sign-ins a minute, and a copier that signs in per request runs out of them within seconds. Sign in once and keep the session — expires_in is 604800 seconds — and re-authenticate on 401, not on every action. Note that polling GET /api/v1/orders spends the trading bucket, so a tight reconciliation loop can exhaust the 120 calls a trade action shares. On a 429, wait for Retry-After (plus a little jitter) before retrying; retrying immediately spends the next window too.

  • Keep the session token outside browser code and public repositories.
  • Use a unique client_request_id for every intended OPEN action.
  • Reuse the same identifier when retrying an OPEN request after a timeout.
  • Process stream events by eventId and ignore duplicates.
  • Resume the stream from the last acknowledged cursor after reconnecting.
  • Apply position-size and symbol allowlists in the copier before submitting a trade.
  • Check marketOpen and read the current quote before you price a limit or stop order.
  • Request a new market token before its 300-second expiry, and treat a rejected market data connection as a signal to re-mint rather than to retry in a loop.
  • Treat rememberedToken values like passwords: they create a session without one.
  • Back off with jitter when the server returns an overload, a 429, or a temporary availability error, and read Retry-After when it is present.
  1. Connect the source

    Sign in with an investor password when the integration only needs to observe the source account.

  2. Read the snapshot

    Fetch the account and current positions before consuming live events.

  3. Open the event stream

    Supply the last processed cursor so reconnects replay missed position events.

  4. Apply copier rules

    Map symbols, calculate destination volume, enforce limits, and reject unsupported trades.

  5. Execute on the destination

    Sign in with the destination trading password and submit an idempotent trade action.

  6. Reconcile

    Regularly compare the destination position snapshot with the copier’s expected state.

{
"error": {
"code": "TRADING_ACCESS_REQUIRED",
"message": "Trading password authentication is required for trade actions"
}
}

The code is stable and is what an integration should branch on; the message is written for a person and can be reworded.

Status Meaning
400 The request did not validate, or the trade was refused before execution: a missing clientOrderId, a malformed timestamp, a volume off the lot step (INVALID_LOT_STEP), a volume outside the instrument’s range (VOLUME_OUT_OF_RANGE), protection levels the market has already passed (INVALID_STOP_LOSS), not enough free margin (INSUFFICIENT_FREE_MARGIN), or an account already restricted by margin risk (ACCOUNT_RISK_RESTRICTED). Transport-level validation failures carry VALIDATION_ERROR.
401 The session token is missing, expired, or revoked. Sign in again.
403 The session is valid but not allowed to do this — an investor session trading, or a missing scope.
404 The record or symbol does not exist on this server.
409 A state conflict: an identifier reused with different details (CLIENT_ORDER_ID_REUSED, ACTION_ID_REUSED), or a filled or already-cancelled order you tried to cancel or modify.
413 The request body is larger than 1 MB. REQUEST_TOO_LARGE.
429 A rate limit bucket is full. RATE_LIMITED, with Retry-After.
500 The server failed. Retry with a backoff.
503 A dependency is unavailable — market data, an FX conversion rate, or a stale quote. MARKET_UNAVAILABLE, QUOTE_UNAVAILABLE, or FX_CONVERSION_UNAVAILABLE. Retry; the trade was not placed.