REST API
Read your TradeSgnl signals, deliveries, accounts and performance, or send a signal, from any tool that can make an HTTP request.
Overview
Every tool the MCP server offers is also a plain JSON endpoint under https://portal.tradesgnl.com/api/v1/. Use it from a spreadsheet, a WordPress table plugin, a cron job or your own code.
The REST API is part of TradeSgnl Cloud, included on the Advanced and Professional plans.
The full list of endpoints, parameters and response shapes is published as an OpenAPI document at https://portal.tradesgnl.com/openapi/v1.json. Postman, Insomnia or any OpenAPI client can be pointed at that URL directly. This page is the shorter path: what the data looks like, how to send a signal, and a few ready-made recipes.
Authentication
Create a key in the portal under API & MCP Server > API Keys (it begins with tsk_ and is shown once), then send it as a Bearer token on every request:
curl "https://portal.tradesgnl.com/api/v1/signals?limit=20" \
-H "Authorization: Bearer tsk_YOUR_KEY"A key is read-only unless you enabled trading when you created it. You can also cap the lot size and restrict the symbols a key may trade; those limits are enforced before anything reaches your accounts, and you can change or revoke them at any time.
Endpoints
| Method and path | What it does |
|---|---|
GET /api/v1/signals | Signal log, most recent first (symbol, command, limit, offset, date_from, date_to, include_deliveries) |
GET /api/v1/deliveries | Per-account delivery status (signal_id, status, limit, offset, date_from, date_to) |
GET /api/v1/clients | Your accounts, connected first (status) |
GET /api/v1/clients/settings | EA input values of one account (client_id) |
GET /api/v1/analytics | Balance and equity per account, as of the EA's last report (client_id) |
POST /api/v1/analytics/refresh | Make the EA upload a fresh report and wait for it (client_id) |
GET /api/v1/live/account | Live balance, equity, margin, open positions, pending orders and trading permissions from a Cloud Terminal (client_id) |
GET /api/v1/live/history | Closed trades, realized P&L and unfilled orders over the last hours, from a Cloud Terminal (client_id, hours, limit) |
GET /api/v1/live/calc | Margin and profit of a hypothetical order, computed by the broker's terminal (client_id, symbol, side, volume, price_open, price_close) |
GET /api/v1/live/logs | MT5 journal or Experts log lines from a Cloud Terminal (client_id, source, lines, contains) |
GET /api/v1/trades | Positions for one account (client_id, limit, offset, date_from, date_to) |
GET /api/v1/performance | Performance metrics (client_id) |
GET /api/v1/performance/breakdowns | Breakdowns by symbol, direction, strategy, day (client_id, breakdown_type, date_from, date_to) |
GET /api/v1/backtests | Backtest results (symbol, limit) |
GET /api/v1/alerts | Alert history (alert_type, limit, offset, date_from, date_to) |
GET /api/v1/alerts/preferences | Alert preferences |
GET /api/v1/subscription | Your plan and connection counts |
GET /api/v1/copiers | Your Trade Copiers and whether each is copying |
GET /api/v1/copiers/activity | Copied trades of one Trade Copier (copier_id, status, limit, offset, date_from, date_to) |
GET /api/v1/licenses | Licence codes |
GET /api/v1/presets | Saved signal presets |
GET /api/v1/docs/search | Search this documentation (query) |
GET /api/v1/market/symbols | Broker symbol catalog (client_id, query) |
GET /api/v1/market/symbol | Contract specification of one symbol: contract size, tick value, lot step, swaps (client_id, symbol) |
GET /api/v1/market/candles | Live candles from 1 minute to monthly, with paging (client_id, symbol, interval, count, before) |
GET /api/v1/market/quote | Live bid, ask, last and tick time (client_id, symbol) |
GET /api/v1/market/quotes | Live quotes for up to 50 symbols in one call (client_id, symbols) |
GET /api/v1/market/ticks | Tick history for one symbol, with paging (client_id, symbol, from, count) |
GET /api/v1/analyst/report | Run the Market Analyst (client_id, symbol, style). Spends AI credits |
POST /api/v1/signals | Send a trade signal. Needs a key with the trade permission |
Query parameters map one to one onto the tool inputs; numbers and booleans are read from the query string. The live/* and market/* endpoints and analyst/report read from the account's Cloud Terminal, so client_id must be an account that has one running; it can be left out when exactly one of your accounts qualifies.
Reading the data
Account figures come from the EA's periodic report, not a live read: analytics, trades and performance carry a reported_at (and trades a report_age_minutes). For numbers right now there are two paths. The live/* endpoints read straight from the account's Cloud Terminal (see Live account data). POST /api/v1/analytics/refresh makes the EA upload a fresh report, waits up to 20 seconds for it to land, and returns the new reported_at; one refresh per account per minute.
List endpoints take limit and offset for paging and date_from / date_to as YYYY-MM-DD, both inclusive. Every response is a JSON object; lists sit under a named key (signals, clients, trades, ...) alongside counts such as returned and offset.
Numbers on a signal (volume, sl, tp) come with a units object that says how to read them, because the same field can hold lots or a percent of balance, pips or a price, depending on how the signal was written. params holds every parameter exactly as it was sent. Error codes on signals, deliveries and alerts come with an error_message that spells out what the code means.
A signal with its per-account deliveries, from GET /api/v1/signals?limit=1&include_deliveries=true:
{
"signals": [
{
"id": "BEIJ9",
"license_code": "1234567890",
"symbol": "EURUSD",
"command": "buy",
"volume": 0.1,
"sl": 30,
"tp": 60,
"comment": "Strategy1",
"error": null,
"error_message": null,
"timestamp": "2026-09-09T00:40:59.8+00:00",
"source": "mcp",
"params": { "vol_lots": 0.1, "sl_pips": 30, "tp_pips": 60, "comment": "Strategy1" },
"units": { "volume": "lots", "sl": "pips", "tp": "pips" },
"deliveries": [
{
"client_id": "ICM12345678",
"status": "executed",
"error": null,
"error_message": null,
"created_at": "2026-09-09T00:41:00.2+00:00",
"ticket": 987654321
}
]
}
],
"returned": 1,
"offset": 0
}executed means the account carried the signal out: an order opened or was placed (it carries its ticket), or a close, cancel or modify was applied (note says what, such as "Closed 2 positions" or "Nothing to close"). received means the account got the signal but reported no outcome: an EA older than 0.74 reports nothing more for closes, cancels and modifies, so treat it as delivered, not as proof a trade opened. A received delivery that carries a ticket did open that order. rejected and failed mean nothing was placed, and error_message says why. ticket is the broker's ticket for the position an entry opened; it is what links a signal to a row in GET /api/v1/trades. It is recorded by EA 0.66 or newer on signals from 2026-09-09 onward, and only when the signal opened a position or placed a pending order, so it is null on older deliveries and on closes. Check ea_version in GET /api/v1/clients.
Your accounts, from GET /api/v1/clients (trimmed):
{
"clients": [
{
"client_id": "ICM12345678",
"broker_name": "IC Markets",
"account_number": "12345678",
"account_type": "demo",
"status": "connected",
"connected_at": "2026-09-01T08:12:44+00:00",
"disconnected_at": null,
"last_seen": "2026-09-09T06:58:03+00:00",
"cloud_terminal": { "status": "active", "is_trial": false, "trial_expires_at": null }
}
],
"total": 3,
"connected": 1,
"disconnected": 2
}Every account that ever connected is listed; pass status=connected for the live ones only. client_id is the id every other endpoint takes. cloud_terminal is null for an account running the EA on your own machine or VPS.
Live account data
These endpoints read the MT5 terminal on the account's Cloud Terminal at the moment of the call.
Account, positions and pending orders
GET /api/v1/live/account returns balance, equity, margin and open P&L, the open positions with their current price, commission and fee, and the pending orders. Two groups say what stands between an order and the broker:
trading:trade_allowedis false on an investor (read-only) login,algo_trading_enabledis the terminal's Algo Trading button,margin_modeishedgingornetting, andping_msis the latency to the broker.stop_out:margin_call_levelandstop_out_level, as a margin level percent, or as account money whenmodeismoney.
On every position and order, opened_by_ea (or placed_by_ea) is true when it carries the EA's magic number and false when it does not, so manual trades stand out. It is null until the EA has reported its settings. opened_via says where it was placed: expert, desktop, mobile or web.
Closed trades
GET /api/v1/live/history?hours=24 returns a summary of realized profit, swap, commission, fee and net over the window, the newest closed trades (limit, up to 100) with the reason each one closed (sl, tp, stop_out, expert, ...), and ended_orders: pending orders that were canceled, rejected or expired. hours goes up to 168; for older history use GET /api/v1/trades.
Margin and profit calculator
GET /api/v1/live/calc?symbol=XAUUSD&side=buy&volume=0.5&price_close=2390 asks the account's terminal what the order needs and makes, using the broker's own rules for leverage, margin currency and hedged margin:
{
"symbol": "XAUUSD",
"side": "buy",
"volume": 0.5,
"currency": "USD",
"price_open": 2401.37,
"price_close": 2390,
"margin": 1200.69,
"profit": -568.5,
"margin_free": 9850
}price_open defaults to the price the order would fill at now. Pass your stop loss as price_close to get the money at risk. Nothing is sent.
Terminal logs
GET /api/v1/live/logs returns the newest lines of the MT5 journal (source=journal: broker connection, authorization, trade requests and their results, the EA loading) or of the Experts log (source=experts: what the EA printed). contains filters lines and lines goes up to 500. Logs read even while the terminal is disconnected from the broker, which is when they help most.
Market data
The market/* endpoints read from the MT5 terminal the account runs on, so prices, times and contract specifications are your broker's own. The symbol, candle and quote endpoints were first published under analyst/, and those paths still answer.
Quotes
From GET /api/v1/market/quote?symbol=XAUUSD:
{
"client_id": "ICM12345678",
"broker_label": "IC Markets 12345678",
"symbol": "XAUUSD",
"bid": 2401.12,
"ask": 2401.37,
"last": null,
"spread_points": 25,
"price": 2401.12,
"price_basis": "bid",
"change_percent": 0.42,
"day_open": 2391.08,
"tick_time": "2026-09-14T08:15:02.418Z",
"tick_age_seconds": 0.6,
"time_basis": "utc",
"trade_mode": "full"
}tick_time is when the broker's latest tick arrived and tick_age_seconds how long ago that was, so a closed market shows up as a large age. price is the price the symbol's candles are built from (see price_basis below) and change_percent runs from today's daily open. last is the last trade price, set only for exchange-traded instruments. trade_mode is full when the symbol can be traded; long_only, short_only, close_only and disabled mean the broker restricts new trades.
Candles
interval is one of 1min, 5min, 15min, 30min, 1h, 2h, 4h, 1day, 1week or 1month, and count goes up to 5,000 bars, newest last. For older history, pass the time of the oldest bar you hold as before and repeat; every page holds only bars strictly older than it.
{ "time": "2026-09-14T08:14:00Z", "open": 2400.95, "high": 2401.40, "low": 2400.81, "close": 2401.12, "volume": 212, "real_volume": 0, "spread": 25 }timeis the bar's open time.- Prices follow the response's
price_basis:bidfor forex, metals and CFDs, which is how MT5 builds their charts, andlastfor exchange-traded instruments. volumeis tick volume, the number of price updates in the bar.real_volumeis traded volume where the broker reports one and 0 otherwise.spreadis the bar's spread in points.
Times on quotes and candles are UTC when time_basis is utc, which is the normal case. It reads server only when the terminal could not measure the broker's clock offset yet (a terminal started over a weekend), and the times are then the broker's clock written as UTC.
Contract specification
GET /api/v1/market/symbol?symbol=XAUUSD returns what position sizing needs: contract_size, tick_size and tick_value, volume_min, volume_max and volume_step, stops_level_points, the filling_modes and order_types the broker accepts, swap_long, swap_short and triple_swap_day, and margin figures. tick_value is the money one tick_size move is worth on one lot, in tick_value_currency (the account currency), so a price move is worth move / tick_size * tick_value * lots. The specification is read live, so tick_value follows the exchange rate for symbols quoted in another currency.
Several quotes at once
GET /api/v1/market/quotes?symbols=EURUSD,XAUUSD,US30 returns up to 50 quotes in one call, each with the fields above. A symbol the account does not offer, or that has no price, appears under errors (unknown_symbol, no_data) instead of failing the whole call.
Ticks
GET /api/v1/market/ticks?symbol=XAUUSD returns the newest ticks of the last hour (count, up to 10,000). Each tick has time to the millisecond, bid, ask, last (exchange-traded instruments), volume, and flags naming what changed. For a range, pass from as an ISO UTC time: ticks come oldest first, and when next_from is set, pass it as from to get the next page with nothing skipped or repeated.
Sending a signal
curl -X POST https://portal.tradesgnl.com/api/v1/signals \
-H "Authorization: Bearer tsk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"license_code": "1234567890",
"symbol": "EURUSD",
"action": "buy",
"volume": { "lots": 0.1 },
"stop_loss": { "pips": 30 },
"take_profit": { "pips": 60 },
"comment": "Strategy1",
"idempotency_key": "order-2026-09-09-001"
}'The response is a receipt: the exact signal syntax that was sent, the connected accounts it went to, and the signal_id to check afterwards.
{
"sent": true,
"syntax": "1234567890,EURUSD,buy,vol_lots=0.1,sl_pips=30,tp_pips=60,comment=Strategy1",
"license_code": "1234567890",
"signal_id": "BEIJ9",
"idempotency_key": "order-2026-09-09-001",
"accounts": [
{ "client_id": "ICM12345678", "broker_name": "IC Markets", "account_number": "12345678", "account_type": "demo" }
],
"connected_accounts": 1,
"server_response": { "signalId": "BEIJ9" },
"next": "The server accepted the signal; the accounts listed execute it next. ..."
}Then read GET /api/v1/deliveries?signal_id=BEIJ9 to see whether each account executed it, or GET /api/v1/signals?include_deliveries=true to get signals and their per-account status in one call. Give the EA a moment before checking.
Two optional fields make sending safer:
-
"dry_run": truevalidates the order and returns the syntax and the accounts it would reach, without sending anything. The receipt hassent: false. For an entry sized in lots it also carriesrisk, priced on each account's Cloud Terminal the way the EA will place the order (symbol mapping, prefix and suffix, pip size included):"risk": [ { "client_id": "ICM12345678", "symbol": "EURUSD", "side": "buy", "volume": 0.1, "entry_price": 1.1002, "entry_is_estimate": true, "stop_loss_price": 1.0972, "take_profit_price": 1.1062, "currency": "USD", "margin_required": 220.04, "margin_free": 9850, "loss_at_stop_loss": -30, "profit_at_take_profit": 60 } ]A market order fills at whatever the price is when it arrives, so
entry_is_estimateis true. An account the preview cannot price, such as one whose broker does not offer the symbol, gets anoteinstead of figures. Volumes given as a percent of balance or equity are not priced: the EA turns them into lots when the signal arrives. -
"idempotency_key"(any unique tag, 8 to 128 characters of letters, digits,_and-) makes a retry safe. If your request times out after the order was already placed and you send it again with the same key, the server returns the original receipt withduplicate: trueinstead of placing a second order. Keys are kept for 24 hours; use a fresh key for each new order.
Actions
| Action | What it does | Fields |
|---|---|---|
buy, sell | Open at market | volume; optional stop_loss, take_profit |
buystop, buylimit, sellstop, selllimit | Place a pending order | volume, entry; optional stop_loss, take_profit |
closebuy, closesell | Close positions on one side of the symbol | optional partial |
closeall | Close every position on the symbol | |
cancellong, cancelshort | Cancel pending orders on one side | |
modifylong, modifyshort | Change the stop loss and take profit of open positions on one side | at least one of stop_loss, take_profit; stop_loss also takes { "entry": true } to move it to each position's open price |
modifybuystop, modifybuylimit, modifysellstop, modifyselllimit | Change the entry, stop loss and take profit of unfilled orders of that type | at least one of entry, stop_loss, take_profit |
closelongsell, closeshortbuy, closelongbuy, closeshortsell | Close one side, then open the other | volume; optional stop_loss, take_profit |
Each field is an object with exactly one key: volume is { "lots": n }, { "percent_balance": n } or { "percent_equity": n }; entry is { "pips": n } from the current price or { "price": n }; stop_loss and take_profit are { "pips": n }, { "points": n } (a price distance) or { "price": n }; partial is { "lots": n } or { "percent": n } from 1 to 100. comment (up to 31 characters) is optional on every action and groups trades: a close action with a comment only touches positions that carry the same comment.
Taking a level off a position is not available in JSON: send sl=none or tp=none in the comma-separated signal format.
Recipes
Google Sheets
IMPORTDATA cannot send a header, so use a short Apps Script instead. In your sheet open Extensions > Apps Script, paste this, run it once to grant access, then add a time-driven trigger to refresh it on a schedule.
function refreshSignals() {
const res = UrlFetchApp.fetch(
'https://portal.tradesgnl.com/api/v1/signals?limit=50&include_deliveries=true',
{ headers: { Authorization: 'Bearer tsk_YOUR_KEY' } },
);
const { signals } = JSON.parse(res.getContentText());
const sheet = SpreadsheetApp.getActiveSheet();
sheet.clear();
sheet.appendRow(['Time', 'Symbol', 'Action', 'Volume', 'Executed on', 'Error']);
signals.forEach((s) => sheet.appendRow([
s.timestamp, s.symbol, s.command, s.volume,
s.deliveries.filter((d) => d.status === 'executed' || d.status === 'received').length,
s.error_message || '',
]));
}WordPress
Any table plugin that can read a JSON URL with a custom request header works. Point it at the endpoint you want (for example https://portal.tradesgnl.com/api/v1/signals?limit=50), add Authorization: Bearer tsk_YOUR_KEY as a header, and set the JSON root to the list key (signals, clients, trades). Use a read-only key for anything that lives on a public site.
A daily P&L line from cron
GET /api/v1/trades returns a summary alongside the positions. Filtering by date_from limits the closed trades to that day, so this prints today's closed P&L every weekday evening:
0 22 * * 1-5 curl -s "https://portal.tradesgnl.com/api/v1/trades?client_id=ICM12345678&date_from=$(date +\%F)&limit=50" \
-H "Authorization: Bearer tsk_YOUR_KEY" | jq '.summary | {total_profit, closed_trades, win_rate, currency}'Errors and limits
Errors are JSON with an error message that says what to do next and a stable reason code, and use the usual statuses:
| Status | When |
|---|---|
| 400 | Invalid input; error names the field |
| 401 | Missing, revoked or expired key |
| 402 | AI credits exhausted (Market Analyst) |
| 403 | The plan does not include the API, the key lacks the trade permission, or a per-key limit blocked the call |
| 404 | Unknown account or symbol |
| 409 | The account's Cloud Terminal is stopped, or it has none |
| 422 | The terminal could not price the request: no tick for the symbol (no_data), or MT5 could not run the calculation (calc_failed) |
| 429 | Rate limit reached |
| 502 | The signal server rejected the signal or could not be reached; nothing was sent |
Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (a Unix timestamp in seconds). Limits: 60 calls per minute; POST /api/v1/signals 10 per minute and 200 per day. The REST API is part of TradeSgnl Cloud, included on the Advanced and Professional plans.
MCP server
Give Claude, ChatGPT, Cursor, Antigravity or any MCP-capable agent access to your TradeSgnl data, the Market Analyst and your signal webhook. One URL, sign in, allow.
Troubleshooting
Troubleshoot common TradeSgnl issues including connection problems, signal delays, and EA configuration errors.