Developer Docs
Tonpo is an MT5 trading gateway that exposes MetaTrader 5 functionality through a clean REST API and WebSocket interface. This reference covers everything you need to connect, trade, and stream live data.
All API requests go to your gateway host. Replace
{HOST} with your gateway domain in all examples below.
https://{HOST}
Quick Start
The fastest path from zero to a live trade. Uses the Python SDK — install it first:
pip install tonpo
Then run this — you'll have a live trade executing in under 2 minutes:
import asyncio from tonpo import TonpoClient, TonpoConfig config = TonpoConfig( host="your-gateway-host.com", port=443, use_ssl=True, ) async def main(): # Step 1: Create a Tonpo user (do this once — store the credentials) async with TonpoClient.admin(config) as client: user = await client.create_user() print(f"API key: {user.api_key}") # Save this! # Step 2: Provision an MT5 account (do this once per account) async with TonpoClient.for_user(config, user.api_key) as client: acct = await client.create_account( "12345678", # MT5 login "your-password", # MT5 password (encrypted at rest) "ICMarkets-Demo" # Broker server name ) await client.wait_for_active(acct.account_id) # up to 180s print(f"Account ID: {acct.account_id}") # Save this! # Step 3: Trade (use stored api_key + account_id every time) async with TonpoClient.for_user(config, user.api_key) as client: info = await client.get_account_info() print(f"Balance: {info.balance} {info.currency}") result = await client.place_market_buy( "EURUSD", volume=0.10, sl=1.0800, tp=1.0950, ) print(f"Order ticket: {result.ticket}") asyncio.run(main())
Authentication
All authenticated endpoints require an API key sent in the X-API-Key header.
curl https://{HOST}/api/users/me \
-H "X-API-Key: your_api_key_here"
API keys are generated by POST /api/users (no auth required). Store the key securely — it cannot be retrieved after creation, only rotated.
| Endpoint type | Auth required |
|---|---|
GET /health | None — public |
POST /api/users | None — public |
All other /api/* endpoints | X-API-Key header |
Users
{
"user_id": "usr_01hx...",
"api_key": "sk_live_..."
}
Accounts
{
"mt5Login": "12345678",
"mt5Password": "your-password",
"mt5Server": "ICMarkets-Demo",
"region": "eu" // optional
}
{
"account_id": "acc_01hx...",
"auth_token": "..." // one-time token
}
POST /api/accounts again.Trading
{
"symbol": "EURUSD",
"side": "buy", // "buy" | "sell"
"orderType": "market", // "market" | "limit" | "stop"
"volume": 0.10,
"price": 1.0850, // required for limit/stop
"sl": 1.0800, // optional
"tp": 1.0950, // optional
"comment": "my-bot", // optional
"magic": 12345 // optional magic number
}
volume to close fully.{
"ticket": 123456789,
"sl": 1.0820, // optional
"tp": 1.0960 // optional
}
Market Data
System
Python SDK — Installation
pip install tonpo
Requires Python 3.10+. Dependencies: httpx and websockets — both installed automatically.
TonpoClient
The main SDK class. Two factory methods create the two client modes:
from tonpo import TonpoClient, TonpoConfig config = TonpoConfig( host="your-gateway.com", port=443, use_ssl=True, ) # Admin client — no auth, use only for create_user() async with TonpoClient.admin(config) as client: user = await client.create_user() # User client — authenticated, for all trading operations async with TonpoClient.for_user(config, api_key=user.api_key) as client: info = await client.get_account_info()
TonpoConfig options:
| Field | Default | Description |
|---|---|---|
host | "localhost" | Gateway hostname |
port | 8080 | Gateway port |
use_ssl | False | Enable HTTPS/WSS |
request_timeout | 30.0 | HTTP request timeout (seconds) |
connect_timeout | 10.0 | Connection timeout (seconds) |
max_reconnect_attempts | 5 | WebSocket reconnect limit |
SDK — Orders
async with TonpoClient.for_user(config, api_key) as client: # Market orders await client.place_market_buy("EURUSD", volume=0.10, sl=1.0800, tp=1.0950) await client.place_market_sell("GBPUSD", volume=0.05, sl=1.2900) # Limit orders await client.place_limit_buy("EURUSD", volume=0.10, price=1.0820, sl=1.0780) await client.place_limit_sell("XAUUSD", volume=0.01, price=2350.00) # Stop orders await client.place_stop_buy("USDJPY", volume=0.10, price=156.50) await client.place_stop_sell("EURUSD", volume=0.10, price=1.0780) # Manage positions positions = await client.get_positions() await client.close_position(ticket=123456789) await client.close_position(ticket=123456789, volume=0.05) # partial await client.modify_position(ticket=123456789, sl=1.0820, tp=1.0960)
SDK — WebSocket
Subscribe to real-time ticks and register callbacks for positions, orders, and account updates.
async with TonpoClient.for_user(config, api_key) as client: # Register callbacks before subscribing async def on_tick(tick): print(f"{tick.symbol}: bid={tick.bid} ask={tick.ask}") async def on_position(pos): print(f"Position update: #{pos.ticket} P&L={pos.profit}") client.ws.on_tick("EURUSD", on_tick) client.ws.on_position(on_position) # Start streaming await client.subscribe(["EURUSD", "GBPUSD", "XAUUSD"]) # Keep running await asyncio.sleep(3600)
Available WebSocket callbacks:
| Method | Fires when |
|---|---|
ws.on_tick(symbol, cb) | New tick arrives for symbol |
ws.on_quote(symbol, cb) | Bid/ask update for symbol |
ws.on_candle(symbol, tf, cb) | New candle closes on timeframe |
ws.on_position(cb) | Any open position changes |
ws.on_order_result(cb) | Order fill or rejection |
ws.on_account(cb) | Balance or equity update |
Account Lifecycle
MT5 accounts transition through these states after provisioning:
| State | Description |
|---|---|
| created | Account provisioned, waiting for node assignment |
| connecting | Node is starting MT5 and logging into broker |
| active | Logged in and ready for trading |
| disconnected | Connection lost — auto-reconnect in progress |
| reconnecting | Actively attempting to reconnect to broker |
| login_failed | MT5 credentials rejected by broker — check login details |
| deleted | Account deprovisioned — cannot be reused |
await client.wait_for_active(account_id) in the SDK — it polls automatically and raises AccountLoginFailedError if login fails.
WebSocket Protocol
Connect directly to the gateway WebSocket for low-latency real-time data.
wss://{HOST}/ws
Authenticate by sending your API key as a header on connect (or as a query param):
const ws = new WebSocket(`wss://{HOST}/ws?api_key=${apiKey}`); ws.onopen = () => { // Subscribe to real-time ticks ws.send(JSON.stringify({ type: "subscribe", symbols: ["EURUSD", "XAUUSD"], request_id: "req_001" })); }; ws.onmessage = (event) => { const msg = JSON.parse(event.data); if (msg.type === "tick") { console.log(msg.symbol, msg.bid, msg.ask); } };
Incoming message types:
| type | Description |
|---|---|
tick | Bid/ask/last tick for a subscribed symbol |
quote | Bid/ask update |
candle | OHLCV candle close |
position | Open position update |
orderResult | Order fill or rejection result |
account | Balance/equity/margin update |
subscribed | Subscription confirmed |
pong | Ping response |
error | Error with code and message |
{
"type": "tick",
"symbol": "EURUSD",
"bid": 1.08542,
"ask": 1.08556,
"last": 1.08549,
"volume": 12,
"time": 1700000000000
}
Error Handling
All errors return a consistent JSON body:
{
"error": "Invalid or missing API key",
"code": 401,
"type": "authentication_error"
}
| HTTP Code | Type | Meaning |
|---|---|---|
401 | authentication_error | Invalid or missing API key |
403 | authentication_error | Permission denied — check plan limits |
404 | not_found | Account or resource not found |
429 | rate_limit_error | Too many requests — back off and retry |
500 | server_error | Internal gateway error |
Python SDK exceptions:
| Exception | When raised |
|---|---|
AuthenticationError | 401 / 403 response |
AccountNotFoundError | 404 response |
AccountLoginFailedError | MT5 credentials rejected |
AccountTimeoutError | wait_for_active() timed out |
TonpoConnectionError | Network / connection failure |
TonpoResponseError | Unexpected HTTP response |
TonpoError | Base class — catch-all |
Rate Limits
| Plan | API Requests | Trades / day |
|---|---|---|
| Free | 60 / min | 10 |
| Basic | 100 / min | 50 |
| Pro | 500 / min | 200 |
| Enterprise | Custom | Unlimited |
When a rate limit is exceeded the API returns 429 with a Retry-After header indicating how many seconds to wait.
Security
- Credentials encrypted at rest — MT5 login and password are AES-256 encrypted on the gateway. They are never returned in any API response after provisioning.
- TLS everywhere — All HTTP and WebSocket traffic is over TLS 1.2+. Plain HTTP is only for local development.
- API key authentication — Keys are hashed in the database. If you lose a key, rotate it — it cannot be recovered.
- Per-user isolation — Each user's accounts are fully isolated. One user cannot access another user's positions or credentials.
- Key rotation — Rotate your API key at any time via
POST /api/users/me/rotate-key. Old key is immediately revoked.
FAQ
Do I need MT5 installed?
No. Tonpo handles the MT5 connection through your gateway. You only need your MT5 broker credentials to provision an account.
How fast is order execution?
Orders go from API call to broker confirmation in under 100ms when the gateway is co-located near your broker's servers.
What happens when my account disconnects?
The gateway automatically attempts to reconnect. Account status moves to reconnecting and then back to active when the connection is restored. Use WebSocket to monitor status in real time.
Can I use multiple accounts?
Yes. Each POST /api/accounts call provisions an additional account. The number of concurrent accounts depends on your plan.
Is there a Python SDK for other languages?
The official SDK is Python. The REST API and WebSocket protocol are standard — you can integrate from any language using plain HTTP requests.
How do I get support?
Email carbon-crumb-churn@duck.com for technical support or open an issue on GitHub.