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.
Find your API URL
Section titled “Find your API URL”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:
- 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.
- 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-Live30becomeshttps://acmemarkets-live30.server.koneth.com. - Search server discovery. This public endpoint needs no key, accepts an optional
namefilter, and returns at most 50 active servers. ReadapiBaseUrlfrom the entry that matches.
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.
Confirm the URL
Section titled “Confirm the URL”GET {API URL}/api/v1/server/info needs no session and returns the server name, currency, and environment:
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.
Endpoint overview
Section titled “Endpoint overview”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 |
Authenticate
Section titled “Authenticate”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_TOKENAn investor password creates read_only access. The server returns TRADING_ACCESS_REQUIRED if that session calls a trading endpoint.
Read the account
Section titled “Read the account”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.
Read markets, quotes, and candles
Section titled “Read markets, quotes, and candles”Market data is account-scoped: a session can only read the instruments attached to the server that hosts the account. Start by listing them.
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.
Read the current price
Section titled “Read the current price”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.
Read candles
Section titled “Read candles”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.
Read market data directly
Section titled “Read market data directly”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:
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.
Read open positions
Section titled “Read open positions”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 a position
Section titled “Open a position”OPEN submits a market order. The server executes against its authoritative quote, not a client-supplied price.
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.
Modify a position
Section titled “Modify a position”Send an absolute stop-loss value, take-profit value, or both. Send null to remove a level.
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.
Close a position
Section titled “Close a position”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:
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}'Close every position
Section titled “Close every position”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.
Place and manage pending orders
Section titled “Place and manage pending orders”POST /api/v1/orders accepts MARKET, LIMIT, and STOP. A LIMIT or STOP order needs requestedPrice and rests until the market reaches it.
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.
curl "https://YOUR-SERVER.example.com/api/v1/orders?page=1&limit=50" \ --header "Authorization: Bearer SESSION_TOKEN"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.
Read trading history
Section titled “Read trading history”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:
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"Read the totals for a period
Section titled “Read the totals for a period”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:
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.
Read balance and transactions
Section titled “Read balance and transactions”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.
Resume, log out, and read server details
Section titled “Resume, log out, and read server details”| 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 |
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.
Stream position events
Section titled “Stream position events”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=1842The 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.
Rate limits
Section titled “Rate limits”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.
Safety model
Section titled “Safety model”- Keep the session token outside browser code and public repositories.
- Use a unique
client_request_idfor every intendedOPENaction. - Reuse the same identifier when retrying an
OPENrequest after a timeout. - Process stream events by
eventIdand 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
marketOpenand 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
rememberedTokenvalues 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 readRetry-Afterwhen it is present.
Copier flow
Section titled “Copier flow”- Connect the source
Sign in with an investor password when the integration only needs to observe the source account.
- Read the snapshot
Fetch the account and current positions before consuming live events.
- Open the event stream
Supply the last processed cursor so reconnects replay missed position events.
- Apply copier rules
Map symbols, calculate destination volume, enforce limits, and reject unsupported trades.
- Execute on the destination
Sign in with the destination trading password and submit an idempotent trade action.
- Reconcile
Regularly compare the destination position snapshot with the copier’s expected state.
Error response
Section titled “Error response”{ "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. |