API for developers MT4/MT5 trading API

MT4/MT5 trading API: positions, orders, prices and trades

MetaTrader trading API for cloud MT4/MT5 accounts: read balance, positions, orders, history, prices and candles live, then open, modify or close.

Trade and read a cloud MT4/MT5 account

Every cloud MT4/MT5 account in TPcopier runs on its own terminal in our cloud. These endpoints read that terminal live - balance, positions, pending orders, history, symbols, prices, candles - and trade on it: market and pending orders, stop loss and take profit changes, breakeven, partial and full closes. No VPS, no MetaTrader on your side, no SDK: plain HTTPS with your key. Add the account first (POST /api/v1/hosting/accounts, the copier guide); its id is the {aid} below.

An account for your software alone - no channel, no copier: add it on bridge mt-api, with no links, role or sender. Its terminal starts as soon as it is added and the endpoints below trade it. It takes a slot of the MT4/5 copier at once - a free one, else one booked from your wallet, a whole month from that moment, renewing by itself a day before its own end (slots). Its answer carries the slot; GET /api/v1/hosting/accounts lists every account with its own. It is kept as an MT4/5 copier account - "bridge":"mt-copy" in every answer, the one kind of cloud MT4/MT5 account for now. Linked into a pipeline later, it is that pipeline's sender or receiver, and runs while a pipeline uses it.

Broker and server lists - the same Broker and Server lists our own app shows, for your sign-up form. MT5 and MT4 each have their own, never mixed. GET /api/v1/hosting/brokers?platform=MT5&q=exness finds up to 20 brokers by their name or one of their servers, the best match first; &full=1 (no q) gives every broker of that platform at once, for software that searches its own copy. Each broker has its servers - the exact spellings MetaTrader signs in with - and checked: the ones MetaTrader's own directory showed when we checked that broker, or one of our accounts signed in to - show those first. A broker or server that is not in the list is typed by your customer: send the broker's name in "broker" with the account. We keep it for our team, and it joins everyone's list once the account signs in.

# a broker search box: wait for 2 letters and 200 ms after the last key (every call counts toward the key's minute)
curl -H "Authorization: Bearer $TPC_KEY" "https://tpcopier.com/api/v1/hosting/brokers?platform=MT5&q=exness"
#   {"brokers": [{"name": "EXNESS", "servers": ["Exness-MT5Real", "Exness-MT5Real2", ...], "checked": [...]}, ...]}

# every MT4 broker at once
curl -H "Authorization: Bearer $TPC_KEY" "https://tpcopier.com/api/v1/hosting/brokers?platform=MT4&full=1"

# a server the list does not have: typed by your customer, with its broker's name
curl -X POST -H "Authorization: Bearer $TPC_KEY" -H "Content-Type: application/json" \
     -d '{"platform":"mt5","login":"7001235","password":"TRADING_PASSWORD","server":"NewBroker-Live","broker":"New Broker","label":"Client 2","bridge":"mt-api"}' \
     https://tpcopier.com/api/v1/hosting/accounts

On your own website - your page asks your server, and your server asks ours with the key. Never put the TPcopier key in a web page: anyone can read it there. The page then works as our own: a Broker box, a Server list with the checked servers first, and the typed Broker name and Server name fields only when the broker or its server is not listed.

// your server (Node.js + Express) - the key stays here
app.get("/brokers", async (req, res) => {
  const u = new URL("https://tpcopier.com/api/v1/hosting/brokers");
  u.searchParams.set("platform", req.query.platform === "MT4" ? "MT4" : "MT5");    // MT4 and MT5: their own lists
  u.searchParams.set("q", String(req.query.q || "").slice(0, 60));
  const r = await fetch(u, { headers: { Authorization: "Bearer " + process.env.TPC_KEY } });
  res.status(r.status).json(await r.json());
});

<!-- your page -->
<select id="platform"><option>MT5</option><option>MT4</option></select>
<input id="broker" list="brokers" placeholder="Your broker, e.g. Exness" autocomplete="off"><datalist id="brokers"></datalist>
<select id="server" hidden></select>
<div id="typed" hidden><input id="t-broker" placeholder="Broker name"> <input id="t-server" placeholder="Server name"></div>
<script>
const $ = s => document.querySelector(s);
const esc = s => s.replace(/[&<>"]/g, c => ({"&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;"})[c]);
let found = [], timer;
$("#platform").onchange = () => { $("#broker").value = ""; $("#server").hidden = $("#typed").hidden = true; };
$("#broker").oninput = () => { clearTimeout(timer); timer = setTimeout(search, 200); };
$("#server").onchange = () => { $("#typed").hidden = $("#server").value !== ""; };   // "not in this list": typed
async function search() {
  const q = $("#broker").value.trim();
  if (q.length < 2) return;
  const r = await fetch("/brokers?platform=" + $("#platform").value + "&q=" + encodeURIComponent(q));
  found = (await r.json()).brokers || [];
  $("#brokers").innerHTML = found.map(b => '<option value="' + esc(b.name) + '">').join("");
  const b = found.find(x => x.name === q);                 // a broker picked from the list
  const servers = b ? [...b.checked, ...b.servers.filter(s => !b.checked.includes(s))] : [];
  $("#server").innerHTML = servers.map(s => "<option>" + esc(s) + "</option>").join("") +
    '<option value="">My server is not in this list</option>';
  $("#server").hidden = !servers.length;
  $("#typed").hidden = servers.length > 0 || (found.length > 0 && !b);   // nothing found: broker and server typed
}
// on Add: server = $("#server").value || $("#t-server").value, broker = $("#t-broker").value - your server sends
// both to POST /api/v1/hosting/accounts ("server", "broker"), and shows our answer's error if there is one
</script>

After it is added, GET /api/v1/hosting/accounts says how its sign-in went: wrong: "login" - tell your customer Incorrect login or password; wrong: "server" - Incorrect server, pick it from the list; review: true - in process, our team puts it right (states).

# add it - its slot is taken at once (a free one first); the answer: its "id" and its "slot" (from, until, renews)
curl -X POST -H "Authorization: Bearer $TPC_KEY" -H "Content-Type: application/json" \
     -d '{"platform":"mt5","login":"7001234","password":"TRADING_PASSWORD","server":"Broker-Live","label":"Client 1","bridge":"mt-api"}' \
     https://tpcopier.com/api/v1/hosting/accounts
# the account live: balance, equity, margin, free margin, margin level, leverage
curl -H "Authorization: Bearer $TPC_KEY" https://tpcopier.com/api/v1/hosting/accounts/A/account-information

# open positions and pending orders
curl -H "Authorization: Bearer $TPC_KEY" https://tpcopier.com/api/v1/hosting/accounts/A/positions
curl -H "Authorization: Bearer $TPC_KEY" https://tpcopier.com/api/v1/hosting/accounts/A/orders

# buy 0.10 lots of EURUSD at the market, with a stop loss and a take profit
curl -X POST -H "Authorization: Bearer $TPC_KEY" -H "Content-Type: application/json" \
     -d '{"action":"ORDER_TYPE_BUY","symbol":"EURUSD","volume":0.1,"stop_loss":1.0750,"take_profit":1.0950}' \
     https://tpcopier.com/api/v1/hosting/accounts/A/trade

# move the stop loss of position 123456 (its take profit stays), then put it at breakeven, then close half of it
curl -X POST ... -d '{"action":"POSITION_MODIFY","position_id":123456,"stop_loss":1.0800}' .../accounts/A/trade
curl -X POST ... -d '{"action":"POSITION_BREAKEVEN","position_id":123456}' .../accounts/A/trade
curl -X POST ... -d '{"action":"POSITION_PARTIAL","position_id":123456,"volume":0.05}' .../accounts/A/trade

# a pending order, then cancel it
curl -X POST ... -d '{"action":"ORDER_TYPE_BUY_LIMIT","symbol":"XAUUSD","volume":0.01,"open_price":2310.5}' .../accounts/A/trade
curl -X POST ... -d '{"action":"ORDER_CANCEL","order_id":987654}' .../accounts/A/trade

# history of the last 30 days, the broker's symbols, one symbol's specification, its price, its candles
curl -H "Authorization: Bearer $TPC_KEY" "https://tpcopier.com/api/v1/hosting/accounts/A/history?days=30"
curl -H "Authorization: Bearer $TPC_KEY" https://tpcopier.com/api/v1/hosting/accounts/A/symbols
curl -H "Authorization: Bearer $TPC_KEY" https://tpcopier.com/api/v1/hosting/accounts/A/symbols/XAUUSD
curl -H "Authorization: Bearer $TPC_KEY" https://tpcopier.com/api/v1/hosting/accounts/A/symbols/XAUUSD/price
curl -H "Authorization: Bearer $TPC_KEY" "https://tpcopier.com/api/v1/hosting/accounts/A/symbols/XAUUSD/candles?timeframe=H1&count=100"

Symbols are each account's own, found by themselves. Send the usual name - XAUUSD, US30, USOIL, BTCUSD - and each account uses its broker's: XAUUSDc on a cent account, GOLD, DJ30.cash, WTI, BITCOIN. Forex, metals, energies and other commodities, indices, crypto and futures: by the name, the broker's own tags (.c, m, .cash, .f, #), and on MT5 what the broker says each symbol is (its currencies, its description, whether the account may trade it). An account with no spot or cash version of an instrument uses its nearest futures contract. The answer names the symbol used (symbol, and requested for the name you sent); an instrument the account has not got answers 404 with the closest names it has.

Live prices, as they move

One request streams any symbols' prices the moment they move (Server-Sent Events) - read from the account's own terminal, only while you are connected. Up to 20 symbols, each in the account's own name; max_s ends it (3600 at most - connect again for more); five streams per workspace at once.

curl -N -H "Authorization: Bearer $TPC_KEY" \
     "https://tpcopier.com/api/v1/hosting/accounts/A/prices/stream?symbols=XAUUSD,EURUSD,US30"

retry: 1000

data: {"symbol": "XAUUSD", "name": "XAUUSDc", "bid": 4174.12, "ask": 4174.36, "time_msc": 1791817213456, "at": "..."}

data: {"symbol": "EURUSD", "name": "EURUSDc", "bid": 1.16231, "ask": 1.16234, "time_msc": 1791817213461, "at": "..."}

time_msc is the broker's own clock (MT5; on MT4 when we first saw the price). event: missing names the symbols the account has not got; event: error a moment its terminal could not answer - the stream goes on. A browser's EventSource cannot send your key: read the stream with fetch from your server (the key never belongs in a page).

Where an order's time goes. Every trade's answer carries timing, each step in ms: site_ms (tpcopier.com's own checks and record), pickup_ms (until the cloud server took it), terminal_ms (the cloud server's queue to the terminal), broker_ms (MetaTrader's own order_send - the broker's part), ping_ms (that terminal's ping to its broker's trade server), hop_ms (the cloud server's agent and the answer's way back) and total_ms (all of it on tpcopier.com). A slow broker - some demo servers take seconds a trade - shows in broker_ms.

Every trade action

One endpoint, POST /api/v1/hosting/accounts/{aid}/trade, with MetaApi's action names. MetaApi's own field names work too (actionType, openPrice, stopLoss, takeProfit, positionId, orderId), so code written for MetaApi moves over with the address and the key.

actionDoesNeeds
ORDER_TYPE_BUY / ORDER_TYPE_SELLa market order (short: BUY / SELL) symbol, volume; optional stop_loss, take_profit, slippage (points), comment, magic
ORDER_TYPE_BUY_LIMIT, _SELL_LIMIT, _BUY_STOP, _SELL_STOPa pending order the same, and open_price
POSITION_MODIFYa position's stop loss and / or take profit - the one you leave out keeps its valueposition_id
POSITION_BREAKEVENthe stop loss to the open priceposition_id
POSITION_CLOSE_IDcloses the position - give volume to close only that many lotsposition_id
POSITION_PARTIALcloses part of the positionposition_id, volume
POSITIONS_CLOSE_SYMBOL / POSITIONS_CLOSE_ALLcloses every position of a symbol / every positionsymbol / nothing
ORDER_MODIFYa pending order's open price, stop loss and take profit - what you leave out staysorder_id
ORDER_CANCELdeletes a pending orderorder_id

Your own comment on every order. Send comment with any opening order (market or pending) and MetaTrader stores it on the order, the deal and the position - it comes back in positions, orders and history, so your system can find its trades by it. Up to 31 characters (MetaTrader's own limit; longer is cut), plain letters, digits and signs only - other characters are left out. Some brokers add their own text when a stop loss or take profit closes the trade.

The answer is MetaTrader's own: {"ok":true,"code":10009,"code_name":"TRADE_RETCODE_DONE","message":"done","order_id":123456,"deal_id":7890,"price":1.0852,"volume":0.1,"action":"ORDER_TYPE_BUY"}. A refusal by the broker is ok:false with its code - on MT5 10019 no money, 10018 market closed, 10016 invalid stops, 10014 invalid volume, 10027 Algo Trading off; an MT4 account gives MT4's own error numbers (code_name is then empty).

Safety and your own limits

  • Opening is refused while your Stop Trading is on or your workspace is paused (423), and past this account's own API limits.
  • Closing and modifying are never refused by these: they only ever lower risk.
  • Your API limits, per account: PUT /api/v1/hosting/accounts/{aid}/api-limits with {"max_lot":1,"require_sl":true,"max_trades_per_day":20} - the largest lot one order may open, no order without a stop loss, orders a UTC day (0 or false: no limit).
  • Every API trade is logged with its answer: GET /api/v1/hosting/accounts/{aid}/api-orders.

When a call is not answered

StatusMeans
409the account's terminal is not connected right now (starting, reconnecting, stopped) - GET /api/v1/hosting/accounts/{aid} says why
404no such account, position, order or symbol
400something missing or wrong in the request - the message says what
503 / 504our cloud server is not taking live calls this minute / the terminal did not answer within 30 s - try again

Coming from MetaApi?

What you used, and where it is here. Everything runs on our cloud: there is no account to deploy or region to pick - the account runs once it is added and linked.

MetaApi / CopyFactoryTPcopier
create_accountPOST /api/v1/hosting/accounts
get_account, get_accounts... (state, connection status)GET /api/v1/hosting/accounts/{aid}, GET /api/v1/hosting
deploy / undeploy / redeploy / removePOST /api/v1/hosting/accounts/{aid}/action with start / stop / reconnect / delete
get_account_informationGET .../accounts/{aid}/account-information
get_positions / get_ordersGET .../accounts/{aid}/positions / .../orders
get_deals_by_time_rangeGET .../accounts/{aid}/history?days=N - each deal: ticket, order, position_id, symbol, type, entry, volume, price, profit, commission, swap, magic, comment, time (the broker's server time, as candles, positions and orders)
get_symbols / get_symbol_specification / get_symbol_priceGET .../symbols, .../symbols/{symbol}, .../symbols/{symbol}/price
get_historical_candlesGET .../symbols/{symbol}/candles
create_market_buy_order ... cancel_order, modify_position, close_positionPOST .../accounts/{aid}/trade (the table above)
streaming listeners (on_position_updated, on_connected ...)webhooks: position.opened / updated / closed, account.connected / disconnected, copy
CopyFactory strategy / subscribera sender / a receiver: the copier's pipelines
get_subscription_transactions, get_user_logGET /api/v1/hosting/copier/events (?status=failed: what was not copied, and why)

Further reading: MQL5's reference for trade functions.

Endpoints in this guide

Base address https://tpcopier.com. Send your key with every request - Authorization: Bearer tpc_... - up to 120 requests a minute. Open an endpoint for its fields, its answers and an example.

hostingCloud MetaTrader and the MT4/5 trade copier
PATCH/api/v1/hosting/accounts/{aid}Edit Hosted

Change a cloud account in its slot: its name (label), its password (sealed at once; it signs in again). Another login or server is another account: POST .../replace.

Parameters

aid *pathinteger

Request body

{
  "label": "",
  "password": ""
}

Responses

200 Successful Response · 422 Validation Error · 401 402 403 429 - see Errors

Example

curl -X PATCH -H "Authorization: Bearer $TPC_KEY" -H "Content-Type: application/json" \
     -d '{"label": "", "password": ""}' \
     "https://tpcopier.com/api/v1/hosting/accounts/{aid}"
DELETE/api/v1/hosting/accounts/{aid}Delete Hosted

Delete a cloud account: its terminal is closed and its data removed; its slot is free for another account.

Parameters

aid *pathinteger

Responses

200 Successful Response · 422 Validation Error · 401 402 403 429 - see Errors

Example

curl -X DELETE -H "Authorization: Bearer $TPC_KEY" \
     "https://tpcopier.com/api/v1/hosting/accounts/{aid}"
GET/api/v1/hosting/accounts/{aid}Hosted Account

One cloud account: its state, money and open positions as last reported (every few seconds), its margin, free margin and leverage, its API limits and its equity protection.

Parameters

aid *pathinteger

Responses

200 Successful Response · 422 Validation Error · 401 402 403 429 - see Errors

Example

curl -H "Authorization: Bearer $TPC_KEY" \
     "https://tpcopier.com/api/v1/hosting/accounts/{aid}"
GET/api/v1/hosting/accounts/{aid}/account-informationAccount Information

Live from the account's terminal: login, name, server, currency, leverage, balance, equity, margin, free margin, margin level and floating profit.

Parameters

aid *pathinteger

Responses

200 Successful Response · 422 Validation Error · 401 402 403 429 - see Errors

Example

curl -H "Authorization: Bearer $TPC_KEY" \
     "https://tpcopier.com/api/v1/hosting/accounts/{aid}/account-information"
GET/api/v1/hosting/accounts/{aid}/positionsAccount Positions

Live: the open positions - ticket, symbol, side, lots, open and current price, stop loss, take profit, profit, swap, magic, comment, open time.

Parameters

aid *pathinteger

Responses

200 Successful Response · 422 Validation Error · 401 402 403 429 - see Errors

Example

curl -H "Authorization: Bearer $TPC_KEY" \
     "https://tpcopier.com/api/v1/hosting/accounts/{aid}/positions"
GET/api/v1/hosting/accounts/{aid}/ordersAccount Orders

Live: the pending orders - ticket, symbol, type (buy_limit, sell_limit, buy_stop, sell_stop), lots, open price, stop loss, take profit, magic, comment, time.

Parameters

aid *pathinteger

Responses

200 Successful Response · 422 Validation Error · 401 402 403 429 - see Errors

Example

curl -H "Authorization: Bearer $TPC_KEY" \
     "https://tpcopier.com/api/v1/hosting/accounts/{aid}/orders"
GET/api/v1/hosting/accounts/{aid}/historyAccount History

Live: the deals of the last `days` (1 to 365) - each trade's in and out, profit, commission and swap; deposits and withdrawals as type 'balance'.

Parameters

aid *pathinteger
daysqueryinteger

Responses

200 Successful Response · 422 Validation Error · 401 402 403 429 - see Errors

Example

curl -H "Authorization: Bearer $TPC_KEY" \
     "https://tpcopier.com/api/v1/hosting/accounts/{aid}/history"
GET/api/v1/hosting/accounts/{aid}/symbolsAccount Symbols

Live: every symbol the account's broker offers, by the broker's own names.

Parameters

aid *pathinteger

Responses

200 Successful Response · 422 Validation Error · 401 402 403 429 - see Errors

Example

curl -H "Authorization: Bearer $TPC_KEY" \
     "https://tpcopier.com/api/v1/hosting/accounts/{aid}/symbols"
GET/api/v1/hosting/accounts/{aid}/symbols/{symbol}Account Symbol

Live: a symbol's specification - digits, point, contract size, smallest, largest and step lot - and its price. `symbol` is the account's own name for it (XAUUSD on a cent account: XAUUSDc), `requested` the name asked for.

Parameters

aid *pathinteger
symbol *pathstring

Responses

200 Successful Response · 422 Validation Error · 401 402 403 429 - see Errors

Example

curl -H "Authorization: Bearer $TPC_KEY" \
     "https://tpcopier.com/api/v1/hosting/accounts/{aid}/symbols/{symbol}"
GET/api/v1/hosting/accounts/{aid}/symbols/{symbol}/priceAccount Price

Live: the symbol's bid, ask and spread now.

Parameters

aid *pathinteger
symbol *pathstring

Responses

200 Successful Response · 422 Validation Error · 401 402 403 429 - see Errors

Example

curl -H "Authorization: Bearer $TPC_KEY" \
     "https://tpcopier.com/api/v1/hosting/accounts/{aid}/symbols/{symbol}/price"
GET/api/v1/hosting/accounts/{aid}/symbols/{symbol}/candlesAccount Candles

Live: the last `count` (1 to 2000) candles of the symbol - timeframe M1, M5, M15, M30, H1, H4 or D1.

Parameters

aid *pathinteger
symbol *pathstring
timeframequerystring
countqueryinteger

Responses

200 Successful Response · 422 Validation Error · 401 402 403 429 - see Errors

Example

curl -H "Authorization: Bearer $TPC_KEY" \
     "https://tpcopier.com/api/v1/hosting/accounts/{aid}/symbols/{symbol}/candles"
POST/api/v1/hosting/accounts/{aid}/tradeAccount Trade

One trade on the account's terminal. action: ORDER_TYPE_BUY / _SELL (market), ORDER_TYPE_BUY_LIMIT / _SELL_LIMIT / _BUY_STOP / _SELL_STOP (pending, with open_price), POSITION_MODIFY (stop_loss / take_profit of position_id), POSITION_BREAKEVEN, POSITION_CLOSE_ID (volume: part of it), POSITION_PARTIAL, POSITIONS_CLOSE_SYMBOL, POSITIONS_CLOSE_ALL, ORDER_MODIFY, ORDER_CANCEL (order_id). The answer is MetaTrader's: ok, code, code_name, message, order_id, deal_id - a refusal by the broker is ok:false with its code.

Parameters

aid *pathinteger

Request body

{
  "action": "ORDER_TYPE_BUY",
  "symbol": "EURUSD",
  "volume": 0.01,
  "open_price": 0,
  "stop_loss": 0,
  "take_profit": 0,
  "position_id": 0,
  "order_id": 0,
  "slippage": 20,
  "comment": "",
  "magic": 0
}

Responses

200 Successful Response · 422 Validation Error · 401 402 403 429 - see Errors

Example

curl -X POST -H "Authorization: Bearer $TPC_KEY" -H "Content-Type: application/json" \
     -d '{"action": "ORDER_TYPE_BUY", "symbol": "EURUSD", "volume": 0.01, "open_price": 0, "stop_loss": 0, "take_profit": 0, "position_id": 0, "order_id": 0, "slippage": 20, "comment": "", "magic": 0}' \
     "https://tpcopier.com/api/v1/hosting/accounts/{aid}/trade"
GET/api/v1/hosting/accounts/{aid}/api-ordersAccount Api Orders

Every trade the API asked this account's terminal for, newest first, with its answer.

Parameters

aid *pathinteger
limitqueryinteger

Responses

200 Successful Response · 422 Validation Error · 401 402 403 429 - see Errors

Example

curl -H "Authorization: Bearer $TPC_KEY" \
     "https://tpcopier.com/api/v1/hosting/accounts/{aid}/api-orders"
PUT/api/v1/hosting/accounts/{aid}/api-limitsAccount Api Limits

The account's own limits for API orders - checked before anything opens; closing is never limited.

Parameters

aid *pathinteger

Request body

{
  "max_lot": 0,
  "require_sl": false,
  "max_trades_per_day": 0
}

Responses

200 Successful Response · 422 Validation Error · 401 402 403 429 - see Errors

Example

curl -X PUT -H "Authorization: Bearer $TPC_KEY" -H "Content-Type: application/json" \
     -d '{"max_lot": 0, "require_sl": false, "max_trades_per_day": 0}' \
     "https://tpcopier.com/api/v1/hosting/accounts/{aid}/api-limits"