Atlas API Reference
Atlas is the orchestration control plane for MetaTrader 5 (MT5) — and, increasingly, for Indian retail brokers (Dhan, Zerodha/Kite, Motilal Oswal). It turns a Windows-only desktop terminal into a programmable, multi-tenant trading platform you drive entirely over HTTP + WebSocket.
This document is the public REST/WebSocket reference. It covers every endpoint a customer
can call with a service key, the exact request/response shapes, curl examples, and an honest
list of what the API cannot do.
Admin / internal endpoints are intentionally excluded. Anything under
/api/admin/*, the runner-facing/api/runner*and/api/runner-pairing/*endpoints, and the credential vault are operator/machine surfaces and are not part of the public API. See What you cannot do.
Official SDKs (coming soon)
We are shipping official client libraries:
@atlas/sdkfor npm (JavaScript/TypeScript) — coming soon.atlas-sdkfor PyPI (Python) — coming soon, as soon as we can.
Until they land, the API is plain JSON over HTTP — use curl, fetch, requests, or any HTTP
client. Every example below is copy-pasteable. When the SDKs ship, this reference stays accurate;
the SDKs are thin wrappers over exactly these endpoints.
Base URL
All endpoints are served from your Atlas deployment's base URL. Throughout this document we use an environment variable so the examples are copy-pasteable:
export ATLAS_BASE_URL="https://api.atlaas.dev" # ← your Atlas API base URL
export ATLAS_API_KEY="atsk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
There is no version prefix in the path today; the API surface is versioned through this document and the upcoming SDKs.
Authentication
Every endpoint (except health and the WebSocket handshake) requires a service key passed as a Bearer token:
Authorization: Bearer atsk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- Service keys look like
atsk_followed by 64 hex characters. - Keys are stored hashed (SHA-256) — the full key is shown once, at creation/regeneration. Store it securely; if you lose it, regenerate it.
- A key belongs to exactly one AtlasAccount (your tenant) and can only access that account's instances and data.
- A key is rejected (
401) if it is unknown, inactive, expired, or if the owning account is inactive, suspended, or out of credits.
Your first service key is created from the Atlas dashboard. Once you have one, you can mint and manage additional keys via the Service keys API.
There is also a privileged master key used by Atlas operators for
/api/admin/*routes. It is not issued to customers and is out of scope here.
Identifiers (read this first)
Atlas uses a few distinct identifiers. Mixing them up is the most common integration mistake, so they're spelled out here:
| Identifier | What it is | Where it appears |
|---|---|---|
| AtlasAccount id | Your tenant/billing account | /api/public/atlas-accounts/{accountId}/service-keys |
MT5 accountId | The MT5 login number of a terminal (e.g. 5012345) | /api/external/instances/{accountId}/... (trading, market data, account) |
Instance id | The Atlas instance row UUID | Subscription endpoints, /api/atlas/instances/{id} |
connectionId | A broker (Dhan/Kite/Motilal) connection ref (= the instance's externalRef) | /api/external/brokers/{broker}/connections/{connectionId}/... |
⚠️ The MT5 trading/market-data endpoints are keyed by the MT5
accountId, but the tick/candle subscription endpoints are keyed by the instanceid(UUID). Both look like/api/external/instances/{x}/...; read each endpoint's parameter description carefully. You can get both values from the instance object returned by list/get instance.
Request & response format
-
Requests and responses are JSON. Send
Content-Type: application/jsonon requests with a body. -
Success responses are wrapped:
{ "success": true, "data": { /* payload */ }, "message": "optional human string" }datais omitted for actions that return nothing (e.g. delete, close). Some endpoints returndataas an array. -
Error responses:
{ "success": false, "error": "Human-readable reason", "errorCode": 1234 }errorCodeis present only for some broker/MT5-level failures.
Errors & status codes
| Status | Meaning | Typical error |
|---|---|---|
200 | OK | — |
201 | Created (instance created, order placed, key created) | — |
400 | Bad request — missing/invalid fields, instance not RUNNING, no runner assigned, invalid JSON | Missing required fields: symbol, type, volume |
401 | Auth failed — missing/invalid/inactive/expired key, suspended account, no credits | Invalid API key / Account has no credits remaining |
403 | Forbidden — instance limit reached, accessing another account's resource | Instance limit reached (5) |
404 | Not found — instance/order/position/key not found or not yours | Instance not found |
409 | Conflict — instance already exists, connection state conflict | An instance with this account ID already exists |
500 | Internal error | Failed to place order |
502 | Broker rejected the command (Indian brokers) | broker error string |
503 | Service unavailable — no available runner, or the runner is offline | Runner is not connected |
504 | Gateway timeout — runner accepted the command but didn't respond in time (outcome unknown) | Broker request timed out… |
Important on
504: for order placement, a504means the command reached the runner but no response came back within the timeout. The order may have executed. Do not blindly retry — reconcile via get positions / get orders first.
Credits, limits & rate limiting
- Credits: every AtlasAccount has a credit balance. When it hits
0the account is suspended and all API calls return401until topped up. Credit top-ups are handled via billing/the dashboard (not the public API). - Instance limit: a service key may carry a
maxInstancescap. Creating beyond it returns403 Instance limit reached. - Rate limits: a key can be configured with
rateLimitPerMinute/rateLimitPerDay. Set these when you create or update a key. - IP allowlist: a key can optionally restrict the source IPs allowed to use it
(
allowedIpAddresses).
Trading preconditions
MT5 trading and market-data calls are proxied in real time to the MT5 terminal through a
connected runner. Before a call can succeed, all of the following must hold, or you'll get a
4xx/503:
- The instance exists and belongs to your account → else
404. - The instance has an assigned runner → else
400 Instance has no assigned runner. - The instance is in the
RUNNINGstate → else400 Instance is not running (state: …). - The runner is currently connected → else
503 Runner is not connected.
Commands are forwarded to the terminal and Atlas waits up to 30 seconds for a reply. If the terminal/runner doesn't answer in time you get a timeout error.
Quick start
# 1. List your MT5 instances
curl -s "$ATLAS_BASE_URL/api/external/instances" \
-H "Authorization: Bearer $ATLAS_API_KEY"
# 2. Check account balance/equity for one instance (by MT5 login)
curl -s "$ATLAS_BASE_URL/api/external/instances/5012345/account" \
-H "Authorization: Bearer $ATLAS_API_KEY"
# 3. Place a market buy of 0.10 lots EURUSD
curl -s -X POST "$ATLAS_BASE_URL/api/external/instances/5012345/orders" \
-H "Authorization: Bearer $ATLAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "symbol": "EURUSD", "type": "BUY", "volume": 0.10 }'
Endpoints
Service keys
Manage the service keys for your own AtlasAccount. The accountId in the URL is your
AtlasAccount id and must match your key's account. The full key (rawKey) is returned only
on create and regenerate.
List service keys
GET /api/public/atlas-accounts/{accountId}/service-keys?isActive=true
curl -s "$ATLAS_BASE_URL/api/public/atlas-accounts/$ACCOUNT_ID/service-keys" \
-H "Authorization: Bearer $ATLAS_API_KEY"
Response (data is an array of keys; never includes the raw key):
{
"success": true,
"data": [
{
"id": "ck_...",
"keyName": "production",
"keyPrefix": "atsk_a1b2c3",
"description": "prod trading bot",
"isActive": true,
"lastUsedAt": "2026-05-25T10:00:00.000Z",
"expiresAt": null,
"maxInstances": 5,
"instanceCount": 2,
"rateLimitPerMinute": 120,
"rateLimitPerDay": 50000,
"requestCount": 18234,
"allowedIpAddresses": [],
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-05-25T10:00:00.000Z"
}
]
}
Create a service key
POST /api/public/atlas-accounts/{accountId}/service-keys
| Field | Type | Required | Notes |
|---|---|---|---|
keyName | string | ✅ | Label for the key |
description | string | ||
maxInstances | number | Cap on instances this key can create | |
rateLimitPerMinute | number | ||
rateLimitPerDay | number | ||
expiresAt | ISO 8601 string | Auto-expire the key | |
allowedIpAddresses | string[] | Source IP allowlist |
curl -s -X POST "$ATLAS_BASE_URL/api/public/atlas-accounts/$ACCOUNT_ID/service-keys" \
-H "Authorization: Bearer $ATLAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "keyName": "trading-bot", "maxInstances": 5 }'
Response (201) — rawKey is shown only here:
{
"success": true,
"data": {
"id": "ck_...",
"keyName": "trading-bot",
"keyPrefix": "atsk_a1b2c3",
"isActive": true,
"maxInstances": 5,
"createdAt": "2026-05-25T10:00:00.000Z",
"rawKey": "atsk_a1b2c3d4e5f6...full-secret...zz"
}
}
Update a service key
PATCH /api/public/service-keys/{keyId} — any subset of: keyName, description, isActive,
maxInstances, rateLimitPerMinute, rateLimitPerDay, expiresAt, allowedIpAddresses.
curl -s -X PATCH "$ATLAS_BASE_URL/api/public/service-keys/$KEY_ID" \
-H "Authorization: Bearer $ATLAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "isActive": false }'
Regenerate a service key
POST /api/public/service-keys/{keyId}/regenerate — issues a new secret and invalidates the old
one. Returns a fresh rawKey.
curl -s -X POST "$ATLAS_BASE_URL/api/public/service-keys/$KEY_ID/regenerate" \
-H "Authorization: Bearer $ATLAS_API_KEY"
Delete a service key
DELETE /api/public/service-keys/{keyId}
curl -s -X DELETE "$ATLAS_BASE_URL/api/public/service-keys/$KEY_ID" \
-H "Authorization: Bearer $ATLAS_API_KEY"
MT5 — Instance lifecycle
An instance is a managed MT5 terminal bound to one broker login. It is identified by its MT5
accountId (login number).
List instances
GET /api/external/instances
curl -s "$ATLAS_BASE_URL/api/external/instances" \
-H "Authorization: Bearer $ATLAS_API_KEY"
{
"success": true,
"data": [
{
"id": "ci_9f8a...",
"accountId": "5012345",
"serverName": "ICMarketsSC-Demo",
"brokerName": "IC Markets",
"name": "alpha-01",
"state": "RUNNING",
"type": "TICKS_TRADE",
"enabled": true,
"lastActiveAt": "2026-05-25T10:00:00.000Z",
"connectedAt": "2026-05-25T09:00:00.000Z",
"errorMessage": null,
"statusMessage": "Connected",
"createdAt": "2026-05-01T00:00:00.000Z",
"updatedAt": "2026-05-25T10:00:00.000Z"
}
]
}
Create an instance
POST /api/external/instances
| Field | Type | Required | Notes |
|---|---|---|---|
accountId | string | ✅ | MT5 login number |
serverName | string | ✅ | MT5 server name (e.g. ICMarketsSC-Demo) |
password | string | MT5 password (stored server-side) | |
brokerName | string | Display label | |
name | string | Display label | |
type | enum | TICK (default), TICKS_TRADE, or TRADE |
curl -s -X POST "$ATLAS_BASE_URL/api/external/instances" \
-H "Authorization: Bearer $ATLAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"accountId": "5012345",
"serverName": "ICMarketsSC-Demo",
"password": "your-mt5-password",
"name": "alpha-01",
"type": "TICKS_TRADE"
}'
Returns 201 with the instance object. A runner is auto-assigned; the instance starts in
PENDING and transitions to RUNNING once the runner deploys it.
Errors: 403 instance limit reached · 409 an instance with this accountId already exists ·
503 no available runner for that server.
Get an instance
GET /api/external/instances/{accountId}
curl -s "$ATLAS_BASE_URL/api/external/instances/5012345" \
-H "Authorization: Bearer $ATLAS_API_KEY"
Start an instance
POST /api/external/instances/{accountId}/start — re-enables and queues the instance for deploy.
curl -s -X POST "$ATLAS_BASE_URL/api/external/instances/5012345/start" \
-H "Authorization: Bearer $ATLAS_API_KEY"
Stop an instance
POST /api/external/instances/{accountId}/stop — disables and stops the terminal.
curl -s -X POST "$ATLAS_BASE_URL/api/external/instances/5012345/stop" \
-H "Authorization: Bearer $ATLAS_API_KEY"
Delete an instance
DELETE /api/external/instances/{accountId}
curl -s -X DELETE "$ATLAS_BASE_URL/api/external/instances/5012345" \
-H "Authorization: Bearer $ATLAS_API_KEY"
MT5 — Account
Get account information
GET /api/external/instances/{accountId}/account — live balance/equity/margin from the terminal.
Requires the instance to be RUNNING (see preconditions).
curl -s "$ATLAS_BASE_URL/api/external/instances/5012345/account" \
-H "Authorization: Bearer $ATLAS_API_KEY"
{
"success": true,
"data": {
"accountId": "5012345",
"balance": 10000.0,
"equity": 10142.5,
"margin": 210.4,
"freeMargin": 9932.1,
"leverage": 500,
"currency": "USD",
"server": "ICMarketsSC-Demo",
"name": "John Trader"
}
}
MT5 — Positions
List open positions
GET /api/external/instances/{accountId}/positions
curl -s "$ATLAS_BASE_URL/api/external/instances/5012345/positions" \
-H "Authorization: Bearer $ATLAS_API_KEY"
{
"success": true,
"data": [
{
"ticket": 123456789,
"symbol": "EURUSD",
"type": "BUY",
"volume": 0.10,
"openPrice": 1.08420,
"currentPrice": 1.08550,
"sl": 0,
"tp": 0,
"profit": 13.0,
"swap": -0.2,
"openTime": 1716631200
}
]
}
Get a single position
GET /api/external/instances/{accountId}/positions/{ticket} — returns 404 if the ticket is not
currently open.
curl -s "$ATLAS_BASE_URL/api/external/instances/5012345/positions/123456789" \
-H "Authorization: Bearer $ATLAS_API_KEY"
Modify a position (SL/TP)
PATCH /api/external/instances/{accountId}/positions/{ticket} — body must include at least one of
sl / tp.
curl -s -X PATCH "$ATLAS_BASE_URL/api/external/instances/5012345/positions/123456789" \
-H "Authorization: Bearer $ATLAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "sl": 1.08000, "tp": 1.09000 }'
Trailing stops are not available over REST — see What you cannot do.
Close a position (full or partial)
POST /api/external/instances/{accountId}/positions/{ticket}/close — send { "volume": <lots> }
for a partial close; an empty body closes the full position.
# Full close
curl -s -X POST "$ATLAS_BASE_URL/api/external/instances/5012345/positions/123456789/close" \
-H "Authorization: Bearer $ATLAS_API_KEY"
# Partial close (0.05 lots)
curl -s -X POST "$ATLAS_BASE_URL/api/external/instances/5012345/positions/123456789/close" \
-H "Authorization: Bearer $ATLAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "volume": 0.05 }'
Close all positions for a symbol
POST /api/external/instances/{accountId}/positions/close-by-symbol — body { "symbol": "EURUSD" }.
curl -s -X POST "$ATLAS_BASE_URL/api/external/instances/5012345/positions/close-by-symbol" \
-H "Authorization: Bearer $ATLAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "symbol": "EURUSD" }'
{
"success": true,
"data": {
"symbol": "EURUSD",
"closedCount": 2,
"totalProfit": 150.50,
"positions": [
{ "ticket": 123456789, "symbol": "EURUSD", "type": "BUY", "volume": 0.10, "profit": 75.25, "closePrice": 1.08550 }
]
}
}
MT5 — Orders
Place an order
POST /api/external/instances/{accountId}/orders
| Field | Type | Required | Notes |
|---|---|---|---|
symbol | string | ✅ | e.g. EURUSD |
type | enum | ✅ | BUY, SELL, BUY_LIMIT, SELL_LIMIT, BUY_STOP, SELL_STOP, BUY_STOP_LIMIT, SELL_STOP_LIMIT |
volume | number | ✅ | Lots |
price | number | Required for limit/stop orders | |
sl | number | Stop-loss price | |
tp | number | Take-profit price | |
deviation | number | Max slippage in points | |
comment | string | Order comment |
# Market buy
curl -s -X POST "$ATLAS_BASE_URL/api/external/instances/5012345/orders" \
-H "Authorization: Bearer $ATLAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "symbol": "EURUSD", "type": "BUY", "volume": 0.10, "sl": 1.08000, "tp": 1.09000 }'
# Pending buy-limit at 1.0800
curl -s -X POST "$ATLAS_BASE_URL/api/external/instances/5012345/orders" \
-H "Authorization: Bearer $ATLAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "symbol": "EURUSD", "type": "BUY_LIMIT", "volume": 0.10, "price": 1.08000 }'
Response (201):
{
"success": true,
"data": { "ticket": 123456789, "symbol": "EURUSD", "type": "BUY", "volume": 0.10, "price": 1.08431, "sl": 1.08000, "tp": 1.09000 }
}
List pending orders
GET /api/external/instances/{accountId}/orders
curl -s "$ATLAS_BASE_URL/api/external/instances/5012345/orders" \
-H "Authorization: Bearer $ATLAS_API_KEY"
Cancel a pending order
DELETE /api/external/instances/{accountId}/orders/{ticket}
curl -s -X DELETE "$ATLAS_BASE_URL/api/external/instances/5012345/orders/123456789" \
-H "Authorization: Bearer $ATLAS_API_KEY"
MT5 — Market data
Get current price
GET /api/external/instances/{accountId}/price/{symbol}
curl -s "$ATLAS_BASE_URL/api/external/instances/5012345/price/EURUSD" \
-H "Authorization: Bearer $ATLAS_API_KEY"
{ "success": true, "data": { "symbol": "EURUSD", "bid": 1.08540, "ask": 1.08552, "last": 0, "volume": 0, "timestamp": 1716631200000 } }
List tradable symbols
GET /api/external/instances/{accountId}/symbols
Returns a bare string[] of symbol names.
curl -s "$ATLAS_BASE_URL/api/external/instances/5012345/symbols" \
-H "Authorization: Bearer $ATLAS_API_KEY"
Get symbol contract specs
GET /api/external/instances/{accountId}/symbols/specs
Superset of /symbols: one object per symbol carrying the full MT5 contract
specification (contract size, volume min/max/step, tick size/value, currencies,
digits, point, trade/calc mode). Values are raw MT5 numbers — derive pip size
and lot value yourself from point + digits. Use this over /symbols when you
need to size forex/CFD positions (gold, metals, index CFDs have contract sizes
other than 100k).
Coverage: specs cover the broker's full symbol catalog (all FX pairs,
metals, indices, crypto — typically several hundred), not just what's visible in
the terminal's Market Watch. One caveat: tickValue is best-effort — MT5 reports
it only for symbols currently in Market Watch and returns 0 otherwise. The
critical fields (contractSize, digits, point, volumeMin/Max/Step,
tickSize, currencies) populate correctly for every symbol; derive pip size and
lot value from point + digits + contractSize.
curl -s "$ATLAS_BASE_URL/api/external/instances/5012345/symbols/specs" \
-H "Authorization: Bearer $ATLAS_API_KEY"
{
"success": true,
"data": [
{
"symbol": "XAUUSD",
"description": "Gold vs US Dollar",
"path": "Metals\\XAUUSD",
"baseCurrency": "XAU",
"profitCurrency": "USD",
"marginCurrency": "USD",
"digits": 2,
"point": 0.01,
"contractSize": 100,
"volumeMin": 0.01,
"volumeMax": 100.0,
"volumeStep": 0.01,
"tickSize": 0.01,
"tickValue": 1.0,
"stopsLevel": 0,
"tradeMode": 4,
"calcMode": 0
}
]
}
Get historical candles
GET /api/external/instances/{accountId}/candles
| Query param | Required | Notes |
|---|---|---|
symbol | ✅ | e.g. EURUSD |
timeframe | ✅ | MT5 format: M1 M2 M3 M4 M5 M6 M10 M12 M15 M20 M30 H1 H2 H3 H4 H6 H8 H12 D1 W1 MN1 |
startTime + endTime | ✅* | ISO 8601 (e.g. 2026-01-01T00:00:00Z) |
count | ✅* | Number of most-recent candles |
* Provide either startTime + endTime or count.
curl -s -G "$ATLAS_BASE_URL/api/external/instances/5012345/candles" \
-H "Authorization: Bearer $ATLAS_API_KEY" \
--data-urlencode "symbol=EURUSD" \
--data-urlencode "timeframe=H1" \
--data-urlencode "count=200"
{
"success": true,
"data": [
{ "time": "2026-05-25T09:00:00.000Z", "open": 1.0840, "high": 1.0858, "low": 1.0835, "close": 1.0851, "tickVolume": 1234, "spread": 1 }
]
}
Calculate margin
POST /api/external/instances/{accountId}/margins
| Field | Type | Required | Notes |
|---|---|---|---|
symbol | string | ✅ | |
type | enum | ✅ | ORDER_TYPE_BUY, ORDER_TYPE_SELL, ORDER_TYPE_*_LIMIT, ORDER_TYPE_*_STOP (note the ORDER_TYPE_ prefix) |
volume | number | ✅ | > 0 |
openPrice | number | ✅ | > 0 |
curl -s -X POST "$ATLAS_BASE_URL/api/external/instances/5012345/margins" \
-H "Authorization: Bearer $ATLAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "symbol": "EURUSD", "type": "ORDER_TYPE_BUY", "volume": 0.10, "openPrice": 1.0850 }'
{ "success": true, "data": { "margin": 21.7, "currency": "USD" } }
MT5 — Real-time (subscriptions & WebSocket)
Real-time data is a two-step model:
- Tell the terminal what to stream with the REST subscription endpoints below
(keyed by instance
id(UUID), not the MT5 login). - Receive the stream by connecting a WebSocket client to
/ws/serviceand subscribing to the relevant channels.
Subscribe to ticks
POST /api/external/instances/{instanceId}/subscribe-tick — body { "symbol": "EURUSD" }.
{instanceId} is the instance id (UUID).
curl -s -X POST "$ATLAS_BASE_URL/api/external/instances/$INSTANCE_ID/subscribe-tick" \
-H "Authorization: Bearer $ATLAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "symbol": "EURUSD" }'
Unsubscribe from ticks
POST /api/external/instances/{instanceId}/unsubscribe-tick — { "symbol": "EURUSD" }, or an
empty body to unsubscribe from all.
List tick subscriptions
GET /api/external/instances/{instanceId}/tick-subscriptions
Subscribe to candles
POST /api/external/instances/{instanceId}/subscribe-candle — { "symbol": "EURUSD", "timeframe": "M15" }.
Unsubscribe from candles
POST /api/external/instances/{instanceId}/unsubscribe-candle — { "symbol", "timeframe" }, or
empty body for all.
List candle subscriptions
GET /api/external/instances/{instanceId}/candle-subscriptions
WebSocket stream — /ws/service
Connect, authenticate with your service key, then subscribe to channels. The server pings
periodically; reply with {"type":"pong"} to stay connected.
const ws = new WebSocket("wss://api.atlaas.dev/ws/service")
ws.onopen = () => {
// 1. Authenticate
ws.send(JSON.stringify({ type: "service_auth", apiKey: process.env.ATLAS_API_KEY, clientName: "my-bot" }))
}
ws.onmessage = (e) => {
const msg = JSON.parse(e.data)
switch (msg.type) {
case "hello_from_atlas": break // greeting; authRequired: true
case "service_auth_success": // { accountId, accountName, ts }
// 2. Subscribe to channels (optionally filter by accountIds / symbols)
ws.send(JSON.stringify({
type: "subscribe",
channels: ["ticks", "candles", "positions", "trades", "orders"],
accountIds: ["5012345"],
symbols: ["EURUSD"]
}))
break
case "service_auth_failed": break // { reason }
case "subscribed": break // echo of your subscription
case "ping": ws.send(JSON.stringify({ type: "pong" })); break
case "mt5_tick": break // { accountId, symbol, bid, ask, ts }
case "mt5_candle": break // { accountId, symbol, timeframe, open, high, low, close, volume, isNew, ts }
case "mt5_position_update": break // { accountId, balance, equity, margin, freeMargin, positions: [...] }
case "mt5_trade_event": break // { accountId, symbol, orderType, orderState, volume, price, ... }
}
}
Subscribe message
{
"type": "subscribe",
"channels": ["ticks", "candles", "positions", "trades", "orders", "broker_ticks", "broker_orders"],
"accountIds": ["5012345"],
"symbols": ["EURUSD"],
"brokerConnectionIds": [],
"brokerSecurityKeys": [],
"brokerOrderConnectionIds": []
}
channels— any ofticks,candles,positions,trades,orders,broker_ticks,broker_orders.accountIds/symbols— optional MT5 filters (empty = everything you own).brokerConnectionIds/brokerSecurityKeys— optional filters for thebroker_tickschannel (Indian brokers). AbrokerSecurityKeyis"<exchangeSegment>:<securityId>".brokerOrderConnectionIds— optional connection filter for thebroker_orderschannel (empty = all your broker connections). Deliberately separate frombrokerConnectionIdsso tick scoping and order scoping can differ.broker_ordersdeliversbroker_order_updatemessages:{ brokerName, connectionId, alert, ts }wherealertis the broker's native order event, untranslated (for Dhan:order_alertData with PascalCase fields likeOrderNo,Status,TxnType,AvgTradedPrice).
Unsubscribe with { "type": "unsubscribe", "channels": [...] }.
Indian brokers (Dhan / Kite / Motilal Oswal)
Atlas can also route per-user retail orders to Indian brokers — Dhan, Zerodha (Kite), and Motilal Oswal — over the same service-key auth. Orders egress from the runner's static IP, which the user has whitelisted with their broker (SEBI compliance), never from a central server.
A broker connection must first be provisioned as an Atlas instance; the connectionId you use
in the trading routes is that instance's externalRef.
Provision a connection
POST /api/atlas/instances
| Field | Type | Required | Notes |
|---|---|---|---|
instanceKind | string | ✅ | dhan, kite, or motilal (also mt5 and other broker kinds) |
runnerId | string | ✅ | The runner that holds the user's whitelisted IP (from your managed setup) |
credentials | object | ✅ | Broker-specific credential blob (encrypted at rest) |
externalRef | string | Your own id for the connection — you reuse this as connectionId | |
externalUserId | string | Opaque user id for runner-side scoping | |
name | string | ||
metadata | object | Non-secret config |
curl -s -X POST "$ATLAS_BASE_URL/api/atlas/instances" \
-H "Authorization: Bearer $ATLAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"instanceKind": "dhan",
"runnerId": "rnr_...",
"externalRef": "my-conn-001",
"credentials": { "clientId": "...", "accessToken": "..." }
}'
Returns { "success": true, "data": { "id": "...", "externalRef": "my-conn-001" } }.
The credentials blob is broker-specific. For Motilal Oswal (instanceKind: "motilal") it is
{ userId, password, apiKey, apiSecretKey, totpSecret, twoFa? } — atlas re-logs in to Motilal
automatically every morning (MOFSL tokens expire daily at 06:00 IST) using the stored TOTP seed, so
no token refresh is needed on your side.
Other instance-management routes (service-key scoped): GET /api/atlas/instances
(filterable by ?instanceKind=&state=&externalUserId=&limit=), GET /api/atlas/instances/{id},
GET /api/atlas/instances/by-external-ref/{externalRef},
POST /api/atlas/instances/by-external-refs ({ "refs": [...] }),
PATCH /api/atlas/instances/{id}, DELETE /api/atlas/instances/{id}.
Provisioning requires a
runnerId, which is part of your managed Atlas onboarding. Broker credential/session initialization itself is performed through admin onboarding, not the public API — see What you cannot do.
Trade
All routes are scoped to /api/external/brokers/{broker}/connections/{connectionId}/... where
{broker} is dhan, kite, or motilal and {connectionId} is the externalRef from
provisioning. The connection's Atlas instance must be RUNNING with its runner connected.
| Method & path | Purpose |
|---|---|
POST …/orders | Place an order (body = the broker's order shape) |
GET …/orders | List orders |
GET …/orders/{orderId} | Get one order |
PUT …/orders/{orderId} | Modify an order (body = changes) |
DELETE …/orders/{orderId} | Cancel an order |
GET …/trades | Trade book / fills (Motilal Oswal) |
GET …/positions | List positions |
GET …/holdings | List demat holdings |
GET …/candles | Historical candles (Dhan/Kite; broker-specific query params) |
POST …/subscriptions | Subscribe to ticks ({ refs: [{ securityId, exchangeSegment }], mode }) |
DELETE …/subscriptions | Unsubscribe ({ refs: [...] }) |
GET …/subscriptions | List desired subscriptions |
# Place a Dhan order (order body follows the broker's own schema)
curl -s -X POST "$ATLAS_BASE_URL/api/external/brokers/dhan/connections/my-conn-001/orders" \
-H "Authorization: Bearer $ATLAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "securityId": "11536", "exchangeSegment": "NSE_EQ", "transactionType": "BUY", "quantity": 1, "orderType": "MARKET", "productType": "INTRADAY" }'
The order/modify request bodies are broker-specific and forwarded to the broker as-is — consult your broker's (Dhan/Kite/Motilal) order documentation for the exact field set. For Motilal Oswal the order body uses MOFSL fields (
exchange,symboltoken,buyorsell,ordertype,producttype,orderduration,price,triggerprice,quantityinlot— note lots, not shares), the order id in the path is MOFSL'suniqueorderid, and modify bodies must includelastmodifiedtime+qtytradedtodayread from the order book. Motilal has no market / tick data —…/candlesand…/subscriptions(and thebroker_ticks/broker_ordersstreams) are not available for it; track order state by polling…/orders/{id}and…/trades. For Dhan/Kite, tick streaming works like MT5: subscribe here, then receive on thebroker_ticksWebSocket channel. Order transitions stream live on thebroker_orderschannel — no runner-side subscribe needed; the feed is always on for every active Dhan connection. A full Motilal integration walkthrough lives indocs/integrations/motilal-oswal.md.
Broker error mapping: 404 connection not provisioned · 409 connection disabled / not running /
ownership mismatch · 400 broker mismatch · 503 runner offline or missing capability ·
502 broker rejected the command · 504 timed out (outcome unknown — reconcile, don't blind-retry).
Health
No authentication required.
| Method & path | Purpose |
|---|---|
GET /health | Liveness — { status, connections, timestamp } |
GET /health/atlas-runner | Runner fleet health (200 healthy, 503 degraded) |
GET /health/data-flow | Per-instance last tick/heartbeat/candle with human-readable "ago" times |
curl -s "$ATLAS_BASE_URL/health"
What you can do
- Manage service keys for your own account (create, list, update, regenerate, delete).
- Manage MT5 instances: create, list, get, start, stop, delete.
- Trade MT5: market & pending orders (limit/stop/stop-limit), cancel orders, modify SL/TP, close (full/partial), close-all-by-symbol.
- Read MT5 state: account balance/equity/margin, open positions, pending orders.
- MT5 market data: live bid/ask, tradable symbols, historical candles, margin calculations.
- Stream in real time over WebSocket: ticks, candles, position updates, trade events, orders.
- Trade Indian brokers (Dhan, Kite, Motilal Oswal) per-user with SEBI-compliant IP egress: place/modify/cancel/list orders, trades, positions, holdings, and (Dhan/Kite) candles and tick subscriptions. Motilal Oswal covers orders + order data only (no market/tick data).
- Provision broker connections (
/api/atlas/instances) within your own account. - Check health without auth.
What you cannot do
The public API is deliberately scoped. These are not available with a customer service key:
- No fleet/admin operations. You cannot create, list, or manage runners or MT5 servers,
view cross-account data, or read system logs. All
/api/admin/*routes require the operator master key. Runners and servers are provisioned/assigned by Atlas; instance creation auto-assigns an available runner. - No account or billing control. You cannot create AtlasAccounts or add credits via the
API — that's handled in the dashboard/billing. An account at
0credits is suspended (401). - No cross-tenant access. A key only ever sees its own account's instances, keys, and data.
Requests for anything else return
404/403. - No trailing stops over REST. Position modification changes SL/TP only. (Trailing-stop behaviour lives inside the MT5 EA, not as a public REST endpoint.)
- No pending-order price edits over REST. The modify endpoint sets SL/TP; it does not move a pending order's entry price.
- No batch order/close endpoints. Submit orders/closes individually.
- No broker credential/session init over the public API. Initializing or removing a
Dhan/Kite/Motilal broker session (
/api/admin/brokers/.../session|refresh|retry) is part of admin onboarding and accepts raw credentials — it is not a public route. - No trading on a non-running instance. Calls require the instance to be
RUNNINGwith its runner connected, or you'll get400/503. - No reading a key's secret after creation. Only the prefix is retrievable; lost keys must be regenerated.
- Runner/EA endpoints are internal.
/api/runner*,/api/runners/*,/api/runner-pairing/*, and the credential vault are machine-to-machine surfaces for the Atlas runner daemon, not for customers.
Appendix: enums & data models
Order types (MT5 — orders endpoint type)
BUY · SELL · BUY_LIMIT · SELL_LIMIT · BUY_STOP · SELL_STOP · BUY_STOP_LIMIT · SELL_STOP_LIMIT
Order types (MT5 — margins endpoint type)
ORDER_TYPE_BUY · ORDER_TYPE_SELL · ORDER_TYPE_BUY_LIMIT · ORDER_TYPE_SELL_LIMIT ·
ORDER_TYPE_BUY_STOP · ORDER_TYPE_SELL_STOP · ORDER_TYPE_BUY_STOP_LIMIT · ORDER_TYPE_SELL_STOP_LIMIT
Candle timeframes (MT5)
M1 M2 M3 M4 M5 M6 M10 M12 M15 M20 M30 H1 H2 H3 H4 H6 H8 H12 D1 W1 MN1
Instance states
PENDING → DEPLOYING → DEPLOYED → RUNNING · STOPPED · ERROR
Trading and market-data calls require RUNNING.
Instance types
TICK (data only) · TICKS_TRADE (data + trading) · TRADE (trading)
WebSocket channels
ticks · candles · positions · trades · orders · broker_ticks · broker_orders
Instance object (MT5)
{
"id": "string (UUID)", // instance id — used by subscription endpoints
"accountId": "string", // MT5 login — used by trading/market-data endpoints
"serverName": "string|null",
"brokerName": "string|null",
"name": "string|null",
"state": "RUNNING", // see Instance states
"type": "TICKS_TRADE", // see Instance types
"enabled": true,
"lastActiveAt": "ISO 8601|null",
"connectedAt": "ISO 8601|null",
"errorMessage": "string|null",
"statusMessage": "string|null",
"createdAt": "ISO 8601",
"updatedAt": "ISO 8601"
}
Position object
{
"ticket": 123456789,
"symbol": "EURUSD",
"type": "BUY", // or "SELL"
"volume": 0.10,
"openPrice": 1.08420,
"currentPrice": 1.08550,
"sl": 0,
"tp": 0,
"profit": 13.0,
"swap": -0.2,
"openTime": 1716631200 // unix seconds
}
Atlas is a Vedus product. This reference documents the public service-key API; operator/admin and runner-internal endpoints are intentionally omitted.