REST API
Read your TradeSgnl signals, deliveries, accounts and performance, or send a signal, from any tool that can make an HTTP request.
Introduction
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, from its last uploaded report (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 | Your licences, each with the accounts that run under it |
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 (up to 50) and offset for paging and date_from / date_to as YYYY-MM-DD, both inclusive, and return the newest rows first. Signals and deliveries are kept for 30 days. Trade history in trades is not pruned. 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
}A signal produces one delivery per account. The status says what that account did:
executed: the account carried the signal out. An entry's row carries itsticket: the position ticket, or the order ticket for a pending order. A close, cancel or modify carries anotesuch as "Closed 2 positions" or "Nothing to close", and no ticket.failed: nothing was placed, or a close or modify only partly succeeded. In the partial case thenotegives how many were done out of how many, anderroris the broker's code.rejected: the account refused the signal, anderror_messagesays why. A modify that matches nothing isrejectedwith code6019. An entry held by the EA's news filter and then dropped isrejectedwith code6023.received: 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. An entry queued by the EA's news filter also readsreceivedwhile it waits. Areceiveddelivery that carries aticketdid open that order.unknown: the account reported a status the server did not recognise. Treat it as unconfirmed.
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 and to the closing deals in GET /api/v1/live/history, and it is the number to pass as ticket when you close or modify that one trade (see Actions). 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.
A delivery is the account's own report of what it did. For conclusive proof of a close, read GET /api/v1/live/account (the position is gone) and GET /api/v1/live/history (the closing deal is there).
Your accounts, from GET /api/v1/clients (trimmed):
{
"clients": [
{
"client_id": "ICM12345678",
"license_code": "1234567890",
"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. license_code is the licence whose signals the account takes. Accounts connected from the portal (cTrader, TradeLocker, crypto exchanges) are listed separately under exchange_connections, with their own license_code. cloud_terminal is null for an account running the EA on your own machine or VPS.
Licences and their accounts
GET /api/v1/licenses returns your licences, oldest first, each with every account that has run under it (trimmed):
{
"licenses": [
{
"code": "1234567890",
"created_at": "2026-05-02T09:14:00+00:00",
"is_preassigned": false,
"accounts": [
{
"client_id": "ICM12345678",
"platform": "mt5",
"broker_name": "IC Markets",
"account_number": "12345678",
"account_type": "demo",
"ea_version": "0.81",
"online": true,
"last_seen": "2026-09-09T06:58:03+00:00"
},
{
"connection_id": "0d6f2c1e-4b0a-4f57-9a7e-2f1c9b8d7e11",
"platform": "ctrader",
"display_name": "Spotware 5896112",
"account_type": "real",
"status": "active",
"enabled": true
}
]
}
],
"total": 1,
"note": "The list shows every account that ever ran under each licence, as last heard. ..."
}An MT5 terminal carries client_id. A cTrader, TradeLocker or crypto connection carries connection_id, the same id clients lists under exchange_connections, plus its status and whether it is switched on (enabled). online is the last state TradeSgnl heard: it stays true until the terminal reports a disconnect. The list also holds accounts that ran under the licence in the past, so use it to see which licence an account belongs to. To see who gets the next signal right now, send a dry run.
Trade history
GET /api/v1/trades?client_id=ICM12345678 returns the account's last uploaded report, not a live read, with one row per position. Rows are newest first. client_id is required. limit defaults to 20 and goes up to 50, offset pages, and date_from / date_to (YYYY-MM-DD) are compared with the close time. An open position has no close time: date_from keeps it and date_to leaves it out. To get a current report first, call POST /api/v1/analytics/refresh.
The response carries reported_at (when the account built the report, in UTC), report_age_minutes, collection_time, the trades list and a summary:
{
"reported_at": "2026-09-09T06:55:00+00:00",
"report_age_minutes": 4,
"collection_time": "2026-09-09T06:55:00+00:00",
"trades": [
{
"ticket": 987654321,
"symbol": "EURUSD",
"type": "Buy",
"volume": 0.1,
"price_open": 1.1002,
"price_close": 1.1062,
"sl": 1.0972,
"tp": 1.1062,
"profit": 60,
"swap": 0,
"commission": -0.7,
"fee": 0,
"comment": "Strategy1",
"open_time": "2026.09.09 00:41:00",
"close_time": "2026.09.09 04:12:37",
"is_open": false
}
],
"summary": {
"total_trades": 214,
"returned": 1,
"offset": 0,
"trades_order": "newest first",
"closed_trades": 213,
"balance_operations_excluded": 2,
"total_profit": 1840.5,
"win_rate": "61.5%",
"currency": "USD",
"balance": 11840.5,
"initial_balance": 10000
}
}ticketis the MT5 position id, the same number a delivery'sticketcarries.typeisBuyorSell.volumeis a closed position's opening size and an open position's current size.price_closeis set on closed rows andnullon open rows.current_priceis set on open rows only, as ofreported_at.slandtpare0when none is set.commissionis summed over the position's deals on closed rows.feeis always0on MT5.open_timeandclose_timeare in the broker's server time, formattedYYYY.MM.DD HH:MM:SS.close_timeisnullwhile the position is open.- History covers the whole account history as of the report.
- The rows do not list individual closing deals. Use
GET /api/v1/live/historyfor those, including each partial close. win_rateis a string such as"61.5%", or"N/A"when no trade has closed.
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 as_of (when it was read), balance, equity, margin and open P&L, the open positions with their current price, commission and fee, and the pending orders. Each open position carries ticket, volume, stop_loss, take_profit, profit, swap, commission, magic, comment and opened_at; stop_loss and take_profit are null when none is set. The ticket is what you pass to close or modify that one trade. 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,broker_connectedis whether the terminal is connected to the broker,margin_modeishedgingornetting, andping_msis the latency to the broker. On a hedging account each entry opens its own position. On a netting account MT5 keeps one position per symbol, with one stop loss and one take profit, so two strategies cannot be managed separately on the same symbol (see Multiple Strategies).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 is read from the terminal at the moment of the call. hours runs from 1 to 168 (default 24) and limit from 1 to 100 (default 20). It returns:
as_ofandtime_basis. Times are UTC whentime_basisisutc.servermeans the broker's clock offset has not been measured yet, and the times are on the broker's clock.summaryof realized profit, swap, commission, fee and net over the whole window, not only the rows listed.closed: one row per closing deal, newest first, withdeal_ticket,position_id,symbol,direction,volume,price_open,price_close,profit,swap,commission,fee,reason(sl,tp,stop_out,expert, ...),reversal,magic,commentandclosed_at. A partial close is its own row.closed_truncated: true when more closes exist thanlimit.ended_orders: pending orders that were canceled, rejected or expired.
position_id equals the delivery's ticket and the ticket in GET /api/v1/trades, so it joins a signal to its fills. For history older than 168 hours 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 accounts it went to (recipients), the accounts under the licence it did not go to (skipped), and the signal_id to check afterwards. Both lists come from the signal server's answer to this very send, not from a lookup 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",
"recipients": [
{
"platform": "mt5",
"client_id": "ICM12345678",
"broker_name": "IC Markets",
"account_number": "12345678",
"account_type": "demo",
"ea_version": "0.81"
},
{
"platform": "ctrader",
"connection_id": "0d6f2c1e-4b0a-4f57-9a7e-2f1c9b8d7e11",
"display_name": "Spotware 5896112",
"account_type": "real"
}
],
"skipped": [],
"connected_accounts": 2,
"server_response": { "signalId": "BEIJ9" },
"next": "The server accepted the signal; the accounts in recipients execute it next. ..."
}recipients are the accounts the signal was handed to: each MT5 terminal whose connection was open, and each cTrader, TradeLocker or crypto connection that is switched on, signed in and on a plan that is active. It is a hand-off, not a result: the deliveries say what each account did. accounts carries the same list as recipients, and connected_accounts is its length; both are kept for integrations written before recipients existed.
skipped lists accounts the signal server knows under the licence that did not get this signal, each with a code and a reason in words:
code | Why the account was skipped |
|---|---|
7021 | Its EA is too old for this action (modify actions need 0.68, a ticket needs 0.71, cancel_at on a pending order needs 0.80). Update the EA. |
7601 | A cTrader account whose sign-in expired. Reconnect it from its card in the portal. |
7701 | A TradeLocker account whose sign-in was refused. Reconnect it from its card in the portal. |
7020 | The signal is an exit and carries noexit, so the exit is dropped and positions close only by stop loss or take profit. |
7016 | The account's connection to the signal server was closing, so nothing was sent to it. |
An account that is offline, switched off or on a lapsed plan is not in either list: the signal server only names accounts it holds right now.
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.
A 200 means the signal was accepted and passed to the accounts in recipients, the ones connected under that licence at that moment. It is not proof of execution: the deliveries say what each account did. A signal is not held for an account that is offline when it is sent. A signal goes to every account connected under the licence. To send to one account only, put that account on its own licence.
Two optional fields make sending safer:
-
"dry_run": truevalidates the order and asks the signal server who would receive it, without sending anything. The server reads the signal exactly as it would a real one (so a syntax or secret error comes back the same way) and picks the accounts with the very same rules, sorecipientsandskippedare the exact live answer for this signal, including the accounts it would leave out and why. Nothing is stored, no alert is raised, and anidempotency_keyis not used up. The receipt hassent: false, and a dry run does not count toward the signal limits. A dry run is limited to 60 a minute per licence (reasonrate_limited).{ "dry_run": true, "sent": false, "syntax": "1234567890,EURUSD,modifylong,sl_price=1.0950", "license_code": "1234567890", "recipients": [ { "platform": "mt5", "client_id": "ICM12345678", "broker_name": "IC Markets", "account_number": "12345678", "account_type": "demo", "ea_version": "0.81" }, { "platform": "ctrader", "connection_id": "0d6f2c1e-4b0a-4f57-9a7e-2f1c9b8d7e11", "display_name": "Spotware 5896112", "account_type": "real" } ], "skipped": [ { "platform": "mt5", "client_id": "EXN87654321", "broker_name": "Exness", "account_number": "87654321", "account_type": "real", "ea_version": "0.67", "code": 7021, "reason": "EA update required for this command: this command needs EA 0.68 or newer, and this account runs 0.67. Update the EA." } ], "connected_accounts": 2, "next": "Nothing was sent. recipients is exactly where the signal server would send it right now, and skipped lists the accounts it would not, with why. ..." }If nothing is connected under the licence and its plan is not active, a real send would be dropped. The dry run says so with
blocked(code7022) and empty lists.For an entry sized in lots the dry run also carries
risk, priced on each recipient with live access (an MT5 account on a Cloud Terminal, or a cTrader account) from that account's own prices and rules, the way the order will be placed (symbol mapping, prefix and suffix, pip size included). A skipped account is not priced. TradeLocker and crypto accounts get no risk figures:"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, or TradeSgnl on an account without one, 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.The same
idempotency_keyfor the same licence within 24 hours returns the original receipt withduplicate: trueand sends nothing. A key is remembered only when the signal reached an account. A send that reached nothing (no account connected) or was rejected is not remembered, so the same key can be retried. A stored signal carries its key inparams.idempotency_keyonGET /api/v1/signals, so you can find a signal by its key.
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, optional ticket |
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; optional ticket |
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; optional ticket |
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, and only goes with closebuy and closesell. The size to close is rounded up to the broker's lot step, and if what would remain is below the broker's minimum lot, the whole position closes. comment (1 to 31 characters: letters, digits, space, _ and -) is optional on every action and groups trades: a close or modify with a comment only touches positions that carry exactly that comment (exact and case-sensitive), and one without a comment touches every position the EA opened on that symbol and side.
ticket acts on one trade only. Give it as a string of digits or an integer. It is allowed with closebuy, closesell (with or without partial) and the six modify actions; any other action with a ticket is refused with 400 and reason invalid_input. For a pending-order modify it is the order's ticket. The ticket narrows the signal, it never widens it: symbol and side (and comment, if sent) must still match the position. A ticket that is no longer open changes nothing. The delivery of the signal that opened the trade carries the ticket, and GET /api/v1/live/account lists it on each open position. It needs EA 0.71 or later and an MT5 account. The MCP send_signal tool takes the same field.
curl -X POST https://portal.tradesgnl.com/api/v1/signals \
-H "Authorization: Bearer tsk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"license_code":"1234567890123","symbol":"EURUSD","action":"closebuy","ticket":"10753414922"}'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. no_cloud_connection means no account has live access at all |
| 404 | Unknown account (account_not_found) or symbol |
| 409 | The account cannot be read: no_cloud_terminal (it has no Cloud Terminal), terminal_offline (its Cloud Terminal is stopped), not_connected or account_paused |
| 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 (rate_limited) |
| 502 | The terminal could not be read (terminal_unavailable), or the signal server rejected the signal (webhook_rejected) or could not be reached (webhook_unreachable); for a signal, nothing was sent |
| 503 | The account could not be read (account_unavailable) |
| 504 | A refresh did not land in time (refresh_timeout) |
Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (a Unix timestamp in seconds). Limits: 60 calls per minute across all endpoints and tools. Within that, POST /api/v1/signals is limited to 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
Fix the most common TradeSgnl problems. The EA will not connect, a signal arrived but did not trade, the lot size or stops are wrong, or trades closed when you did not expect it.