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 => ({"&": "&", "<": "<", ">": ">", '"': """})[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.
action | Does | Needs |
|---|---|---|
ORDER_TYPE_BUY / ORDER_TYPE_SELL | a 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_STOP | a pending order | the same, and open_price |
POSITION_MODIFY | a position's stop loss and / or take profit - the one you leave out keeps its value | position_id |
POSITION_BREAKEVEN | the stop loss to the open price | position_id |
POSITION_CLOSE_ID | closes the position - give volume to close only that many lots | position_id |
POSITION_PARTIAL | closes part of the position | position_id, volume |
POSITIONS_CLOSE_SYMBOL / POSITIONS_CLOSE_ALL | closes every position of a symbol / every position | symbol / nothing |
ORDER_MODIFY | a pending order's open price, stop loss and take profit - what you leave out stays | order_id |
ORDER_CANCEL | deletes a pending order | order_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-limitswith{"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
| Status | Means |
|---|---|
409 | the account's terminal is not connected right now (starting, reconnecting, stopped) - GET /api/v1/hosting/accounts/{aid} says why |
404 | no such account, position, order or symbol |
400 | something missing or wrong in the request - the message says what |
503 / 504 | our 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 / CopyFactory | TPcopier |
|---|---|
create_account | POST /api/v1/hosting/accounts |
get_account, get_accounts... (state, connection status) | GET /api/v1/hosting/accounts/{aid}, GET /api/v1/hosting |
deploy / undeploy / redeploy / remove | POST /api/v1/hosting/accounts/{aid}/action with start / stop / reconnect / delete |
get_account_information | GET .../accounts/{aid}/account-information |
get_positions / get_orders | GET .../accounts/{aid}/positions / .../orders |
get_deals_by_time_range | GET .../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_price | GET .../symbols, .../symbols/{symbol}, .../symbols/{symbol}/price |
get_historical_candles | GET .../symbols/{symbol}/candles |
create_market_buy_order ... cancel_order, modify_position, close_position | POST .../accounts/{aid}/trade (the table above) |
streaming listeners (on_position_updated, on_connected ...) | webhooks: position.opened / updated / closed, account.connected / disconnected, copy |
| CopyFactory strategy / subscriber | a sender / a receiver: the copier's pipelines |
get_subscription_transactions, get_user_log | GET /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.
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 * | path | integer |
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 * | path | integer |
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 * | path | integer |
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 * | path | integer |
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 * | path | integer |
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 * | path | integer |
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 * | path | integer |
| days | query | integer |
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 * | path | integer |
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 * | path | integer |
| symbol * | path | string |
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 * | path | integer |
| symbol * | path | string |
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 * | path | integer |
| symbol * | path | string |
| timeframe | query | string |
| count | query | integer |
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 * | path | integer |
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 * | path | integer |
| limit | query | integer |
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 * | path | integer |
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"