Open Portal

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 pathWhat it does
GET /api/v1/signalsSignal log, most recent first (symbol, command, limit, offset, date_from, date_to, include_deliveries)
GET /api/v1/deliveriesPer-account delivery status (signal_id, status, limit, offset, date_from, date_to)
GET /api/v1/clientsYour accounts, connected first (status)
GET /api/v1/clients/settingsEA input values of one account (client_id)
GET /api/v1/analyticsBalance and equity per account, as of the EA's last report (client_id)
POST /api/v1/analytics/refreshMake the EA upload a fresh report and wait for it (client_id)
GET /api/v1/live/accountLive balance, equity, margin, open positions, pending orders and trading permissions from a Cloud Terminal (client_id)
GET /api/v1/live/historyClosed trades, realized P&L and unfilled orders over the last hours, from a Cloud Terminal (client_id, hours, limit)
GET /api/v1/live/calcMargin 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/logsMT5 journal or Experts log lines from a Cloud Terminal (client_id, source, lines, contains)
GET /api/v1/tradesPositions for one account (client_id, limit, offset, date_from, date_to)
GET /api/v1/performancePerformance metrics (client_id)
GET /api/v1/performance/breakdownsBreakdowns by symbol, direction, strategy, day (client_id, breakdown_type, date_from, date_to)
GET /api/v1/backtestsBacktest results (symbol, limit)
GET /api/v1/alertsAlert history (alert_type, limit, offset, date_from, date_to)
GET /api/v1/alerts/preferencesAlert preferences
GET /api/v1/subscriptionYour plan and connection counts
GET /api/v1/copiersYour Trade Copiers and whether each is copying
GET /api/v1/copiers/activityCopied trades of one Trade Copier (copier_id, status, limit, offset, date_from, date_to)
GET /api/v1/licensesLicence codes
GET /api/v1/presetsSaved signal presets
GET /api/v1/docs/searchSearch this documentation (query)
GET /api/v1/market/symbolsBroker symbol catalog (client_id, query)
GET /api/v1/market/symbolContract specification of one symbol: contract size, tick value, lot step, swaps (client_id, symbol)
GET /api/v1/market/candlesLive candles from 1 minute to monthly, with paging (client_id, symbol, interval, count, before)
GET /api/v1/market/quoteLive bid, ask, last and tick time (client_id, symbol)
GET /api/v1/market/quotesLive quotes for up to 50 symbols in one call (client_id, symbols)
GET /api/v1/market/ticksTick history for one symbol, with paging (client_id, symbol, from, count)
GET /api/v1/analyst/reportRun the Market Analyst (client_id, symbol, style). Spends AI credits
POST /api/v1/signalsSend 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_allowed is false on an investor (read-only) login, algo_trading_enabled is the terminal's Algo Trading button, margin_mode is hedging or netting, and ping_ms is the latency to the broker.
  • stop_out: margin_call_level and stop_out_level, as a margin level percent, or as account money when mode is money.

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 }
  • time is the bar's open time.
  • Prices follow the response's price_basis: bid for forex, metals and CFDs, which is how MT5 builds their charts, and last for exchange-traded instruments.
  • volume is tick volume, the number of price updates in the bar. real_volume is traded volume where the broker reports one and 0 otherwise. spread is 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": true validates the order and returns the syntax and the accounts it would reach, without sending anything. The receipt has sent: false. For an entry sized in lots it also carries risk, 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_estimate is true. An account the preview cannot price, such as one whose broker does not offer the symbol, gets a note instead 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 with duplicate: true instead of placing a second order. Keys are kept for 24 hours; use a fresh key for each new order.

Actions

ActionWhat it doesFields
buy, sellOpen at marketvolume; optional stop_loss, take_profit
buystop, buylimit, sellstop, selllimitPlace a pending ordervolume, entry; optional stop_loss, take_profit
closebuy, closesellClose positions on one side of the symboloptional partial
closeallClose every position on the symbol
cancellong, cancelshortCancel pending orders on one side
modifylong, modifyshortChange the stop loss and take profit of open positions on one sideat 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, modifyselllimitChange the entry, stop loss and take profit of unfilled orders of that typeat least one of entry, stop_loss, take_profit
closelongsell, closeshortbuy, closelongbuy, closeshortsellClose one side, then open the othervolume; 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:

StatusWhen
400Invalid input; error names the field
401Missing, revoked or expired key
402AI credits exhausted (Market Analyst)
403The plan does not include the API, the key lacks the trade permission, or a per-key limit blocked the call
404Unknown account or symbol
409The account's Cloud Terminal is stopped, or it has none
422The terminal could not price the request: no tick for the symbol (no_data), or MT5 could not run the calculation (calc_failed)
429Rate limit reached
502The 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.

On this page