Appearance
Are you an LLM? You can read better optimized documentation at /api/streaming.md for this page in Markdown format
Streaming (SSE)
GET /api/v1/stream is a Server-Sent Events feed. It delivers real-time events as your trading engine produces them — position updates, order fills, TP hits, trade lifecycle changes, and more.
Connecting
bash
curl -N https://app.telegramtometatrader.com/api/v1/stream \
-H "Authorization: Bearer ttmt_live_…" \
-H "Accept: text/event-stream"Optional: filter to one account:
bash
curl -N "https://app.telegramtometatrader.com/api/v1/stream?account_id=a1b2c3d4-..." \
-H "Authorization: Bearer ttmt_live_…" \
-H "Accept: text/event-stream"The account_id must be the DB UUID from GET /accounts, not the MetaAPI string ID.
Each event arrives as:
id: 42
data: {"type":"tp_hit","eventId":42,"timestamp":"2026-06-09T14:32:01.123Z","data":{...}}Connection lifecycle
30-second auth re-check
Every 30 seconds the stream re-validates your key and account access against the database. If your key was revoked or account access was disabled in that window, the stream emits an auth_revoked event and closes. This means revocation cuts off an active stream within 30 seconds.
The check fails-open on transient database errors — a brief Supabase blip will not disconnect a valid stream.
5-minute forced recycle
The stream has an explicit 5-minute lifetime. Before closing, it emits a recycle event:
json
{
"type": "recycle",
"reason": "max_lifetime"
}Then the connection closes. Your client should reconnect immediately, sending the last received id as Last-Event-ID.
Reconnect with Last-Event-ID
The server keeps a buffer of the last 100 events. When you reconnect with Last-Event-ID: N, the server replays any buffered events your client missed. For gaps beyond the buffer, fall back to REST polling.
bash
curl -N https://app.telegramtometatrader.com/api/v1/stream \
-H "Authorization: Bearer ttmt_live_…" \
-H "Last-Event-ID: 41"Browser EventSource sends Last-Event-ID automatically. In Node.js or Python, you need to track the last id value and include it on reconnect.
Error responses
| Code | Reason |
|---|---|
401 | Invalid or revoked key |
409 | Another stream is already open for this key |
503 | Your trading container is not running — REST history still works |
Signal provenance
Every event that touches a position or trade carries a signal sub-object identifying the Telegram message that originated the trade:
json
{
"signal": {
"signal_id": "f3c1a2b4-uuid",
"telegram_message_id": 12345
}
}signal is null when:
- The trade has no signal FK (manual or legacy trade)
- The originating signal was soft-deleted
- The event was emitted before the watchdog cache was populated
breakeven_set always has signal: null — see breakeven_set below.
Event catalog
All events share a common outer envelope:
json
{
"type": "...",
"eventId": 42,
"timestamp": "2026-06-09T14:32:01.123Z",
"userId": "uuid",
"data": { ... }
}position_update
Fired continuously (throttled) as positions move. Use this to update unrealized P&L in your UI. The data may contain either a single position delta or a batch of positions.
Single position delta:
json
{
"type": "position_update",
"eventId": 10,
"timestamp": "2026-06-09T14:32:01.123Z",
"data": {
"positionId": "abc-metaapi-id",
"symbol": "XAUUSD",
"unrealizedProfit": 42.50,
"currentPrice": 3312.80,
"accountId": "a1b2c3d4-uuid",
"signal": {
"signal_id": "f3c1a2b4-uuid",
"telegram_message_id": 12345
}
}
}Batch positions update:
json
{
"type": "position_update",
"eventId": 11,
"timestamp": "2026-06-09T14:32:01.456Z",
"data": {
"positions": [
{
"id": "abc-metaapi-id",
"symbol": "XAUUSD",
"unrealizedProfit": 42.50,
"currentPrice": 3312.80
}
],
"accountId": "a1b2c3d4-uuid"
}
}order_fill
Fired when a limit order fills (a layer entry executes).
json
{
"type": "order_fill",
"eventId": 15,
"timestamp": "2026-06-09T14:33:00.000Z",
"data": {
"orderId": "order-metaapi-id",
"positionId": "pos-metaapi-id",
"symbol": "XAUUSD",
"volume": 0.03,
"fillPrice": 3310.50,
"layer": 2,
"accountId": "a1b2c3d4-uuid",
"signal": {
"signal_id": "f3c1a2b4-uuid",
"telegram_message_id": 12345
}
}
}position_closed
Fired when a position closes (TP hit, SL hit, manual close, breakeven, trailing stop).
json
{
"type": "position_closed",
"eventId": 20,
"timestamp": "2026-06-09T14:35:00.000Z",
"data": {
"positionId": "pos-metaapi-id",
"reason": "tp_hit",
"tradeId": "trade-db-uuid",
"accountId": "a1b2c3d4-uuid",
"signal": {
"signal_id": "f3c1a2b4-uuid",
"telegram_message_id": 12345
}
}
}trade_executed
Fired when a new trade is opened (the signal was processed and orders were placed).
json
{
"type": "trade_executed",
"eventId": 5,
"timestamp": "2026-06-09T14:30:00.000Z",
"data": {
"tradeId": "trade-db-uuid",
"symbol": "XAUUSD",
"channelName": "Gold Signals Pro",
"positionsCount": 3,
"ordersCount": 12,
"accountId": "a1b2c3d4-uuid",
"signal": {
"signal_id": "f3c1a2b4-uuid",
"telegram_message_id": 12345
}
}
}trade_finalized
Fired when a trade reaches a terminal state (all positions closed, P&L computed).
json
{
"type": "trade_finalized",
"eventId": 25,
"timestamp": "2026-06-09T14:40:00.000Z",
"data": {
"tradeId": "trade-db-uuid",
"status": "closed_profit",
"profitLoss": 87.20,
"accountId": "a1b2c3d4-uuid",
"signal": {
"signal_id": "f3c1a2b4-uuid",
"telegram_message_id": 12345
}
}
}tp_hit
Fired when a take-profit level is hit and partial orders close.
json
{
"type": "tp_hit",
"eventId": 18,
"timestamp": "2026-06-09T14:34:00.000Z",
"data": {
"tradeId": "trade-db-uuid",
"tpLevel": "TP2",
"accountId": "a1b2c3d4-uuid",
"signal": {
"signal_id": "f3c1a2b4-uuid",
"telegram_message_id": 12345
}
}
}breakeven_set
Fired when stop-loss is moved to breakeven on a position.
signal is always null for this event
breakeven_set carries a DB position UUID (not the MetaAPI position ID). The signal lookup requires the MetaAPI ID, so signal is always null here.
json
{
"type": "breakeven_set",
"eventId": 19,
"timestamp": "2026-06-09T14:34:30.000Z",
"data": {
"positionId": "pos-db-uuid",
"newSL": 3310.00,
"accountId": "a1b2c3d4-uuid",
"signal": null
}
}breakeven_failed
Fired when a breakeven attempt fails (broker rejected the SL modification).
json
{
"type": "breakeven_failed",
"eventId": 22,
"timestamp": "2026-06-09T14:34:45.000Z",
"data": {
"tradeId": "trade-db-uuid",
"positionId": "pos-db-uuid",
"accountId": "a1b2c3d4-uuid",
"signal": {
"signal_id": "f3c1a2b4-uuid",
"telegram_message_id": 12345
}
}
}auth_revoked
Fired by the 30-second auth re-check when your key is revoked or access is disabled. The stream closes immediately after.
json
{
"type": "auth_revoked",
"eventId": 99,
"timestamp": "2026-06-09T15:00:00.000Z",
"data": {
"reason": "key_revoked"
}
}reason is "key_revoked" or "access_disabled".
recycle
Fired before the server-initiated 5-minute recycle close. Reconnect immediately.
json
{
"type": "recycle",
"eventId": 100,
"timestamp": "2026-06-09T14:35:00.000Z",
"data": {
"reason": "max_lifetime"
}
}Recommended client pattern
Connect, track the last event ID, reconnect on close, fall back to REST on reconnect failure:
javascript
const BASE = "https://app.telegramtometatrader.com/api/v1"
const KEY = "ttmt_live_…"
let lastEventId = null
function connect() {
const headers = {
"Authorization": `Bearer ${KEY}`,
"Accept": "text/event-stream",
...(lastEventId ? { "Last-Event-ID": lastEventId } : {})
}
// fetch-based SSE (Node 18+, browsers)
fetch(`${BASE}/stream`, { headers })
.then(resp => {
if (!resp.ok) throw new Error(`${resp.status}`)
const reader = resp.body.getReader()
const decoder = new TextDecoder()
function read() {
return reader.read().then(({ done, value }) => {
if (done) { reconnect(); return }
const text = decoder.decode(value)
parseSSE(text)
return read()
})
}
return read()
})
.catch(() => reconnect())
}
function parseSSE(text) {
for (const line of text.split("\n")) {
if (line.startsWith("id: ")) lastEventId = line.slice(4).trim()
if (line.startsWith("data: ")) {
const event = JSON.parse(line.slice(6))
handleEvent(event)
}
}
}
function reconnect() {
setTimeout(connect, 2000) // 2s back-off
}
connect()
