const res = await fetch('https://app.trdrs.co/api/account/stream', {
headers: { Authorization: `Bearer ${process.env.TRDRS_API_KEY}` },
})
const decoder = new TextDecoder()
for await (const chunk of res.body!) {
// Each SSE frame: "event: <name>\ndata: <json>\n\n". Snapshot first, then live events.
process.stdout.write(decoder.decode(chunk as Uint8Array))
}curl -N 'https://app.trdrs.co/api/account/stream' \
-H "Authorization: Bearer $TRDRS_API_KEY"import requests
url = "https://app.trdrs.co/api/account/stream"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.trdrs.co/api/account/stream",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://app.trdrs.co/api/account/stream"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://app.trdrs.co/api/account/stream")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.trdrs.co/api/account/stream")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body<string>{
"error": "invalid_instrument"
}{
"error": "invalid_instrument"
}Stream account updates
Streams the account, its risk lock and what it can trade as server-sent events (text/event-stream). Every change arrives as one account event carrying the whole account at one consistent revision, the same body /api/account/snapshot returns, and every connection opens with one. Apply frames by the revision and nothing else: keep the newest for the accountId, drop any at or behind it, and replace your whole picture with any ahead of it.
Required key: Trading API key. A Connect app’s backend can also send it with the app’s API key, and a venue’s backend with its Venue key, which carries trader:read or trader:trade, each naming one of its own traders in x-trdrs-trader: it then runs on that trader’s own accounts.
const res = await fetch('https://app.trdrs.co/api/account/stream', {
headers: { Authorization: `Bearer ${process.env.TRDRS_API_KEY}` },
})
const decoder = new TextDecoder()
for await (const chunk of res.body!) {
// Each SSE frame: "event: <name>\ndata: <json>\n\n". Snapshot first, then live events.
process.stdout.write(decoder.decode(chunk as Uint8Array))
}curl -N 'https://app.trdrs.co/api/account/stream' \
-H "Authorization: Bearer $TRDRS_API_KEY"import requests
url = "https://app.trdrs.co/api/account/stream"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.trdrs.co/api/account/stream",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://app.trdrs.co/api/account/stream"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://app.trdrs.co/api/account/stream")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.trdrs.co/api/account/stream")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body<string>{
"error": "invalid_instrument"
}{
"error": "invalid_instrument"
}Authorizations
The Trading API key (trdrs_sk_…), for market data, orders and account state on the accounts its owner holds. Use it server-to-server, and keep it on your server. Its scope is named trading in the API.
Query Parameters
The provider the account is at. Omit it to use your default provider.
The account number at that provider. It must be one of your own accounts.
The execId of the last execution report you received, so the stream sends the account's report events after it. An EventSource sends the same id as Last-Event-ID when it reconnects on its own, and that header wins when you send both.
Who follows changes made to the account outside trdrs, at the provider. engine (the default): trdrs reads the provider and sends a frame for each change it sees. client: you follow the provider yourself, as a provider whose publicAccountState is true lets you do, and the stream sends the frame it opens with and a frame for each change trdrs makes or reads, such as after an order you place through trdrs.
engine, client Response
An open event stream. Each event's name is its event: line, and its data: line is the body.
account: the whole account at one revision. There is no separate snapshot event, because the first frame is simply the first. A row missing from a frame is closed or cancelled. A frame that skips revisions is still complete, so there is nothing to recover. Never work out a revision of your own. Protection is part of the account, sobracketscomes in the same frame, at the same revision.lock: whether the account is locked, why, and when it unlocks (theRiskLockmodel).trading: what the account can trade (theAccountTradingmodel): whose markets it trades, its gates, and the match of every market in view, which is each market it holds or has a working order on and each one you asked about throughGET /api/trading/matchin the last half hour. It comes after the firstaccountframe and again whenever any of it changes, withversionone higher each time, so keep the one with the highest version. It is its own event because it changes while the account doesn't, such as when a venue halts new entries or a continuous future rolls. When itsfingerprintchanges, readGET /api/account/marketsagain. The markets you asked about on the account are kept for a week, and a stream that opens asks them again for you, so its firsttradingevent carries the market you last looked at.execution{ accountId, execution }: one fill the ledger just recorded on this account, asGET /api/account/executionslists it. Read the route once for the fills before the stream, and add each event's fill by itsid.report: the account's execution reports, in the account's order (theExecutionReportEventmodel).{ kind: "report", accountId, sequence, report }is one report, and itsexecId, the report's identity, is the event'sid:line unless it is anorder_statusreport.{ kind: "status", accountId, sequence, reports }is theorder_statusreport of every order that may still fill: replace what you hold of the account's orders with it. A stream that opens with theexecIdof your last report, asLast-Event-IDorafter, sends every report after it; one that opens without one, or with one the account no longer keeps, sends astatusfirst, and an open stream is sent astatuswhenever its reports could not be carried whole. An account sendsreportevents once its execution reports are on, and none until then.strategy: your live strategy instances, asGET /api/strategy-instanceslists them.{ kind: "list", instances }is the whole set, sent once after the firstaccountframe;{ kind: "instance", instance }is one instance whole after a change;{ kind: "removed", id }names one that no longer exists. Instances are yours rather than one account's, so every stream of yours carries every instance.error{ code, message }: the stream is ending.provider_disconnectedmeans the account couldn't be read when the stream opened,risk_resync_requiredthat the risk state changed faster than the stream could send it or couldn't be followed, andsession_expiredthat the session or key that opened the stream ended. Reconnect for the current account, after signing in again forsession_expired.
The stream also carries alert and copier events for the trdrs app's own alerts and Trader Copier. Skip any event you don't use.
The stream may end with a reconnect event, { code, afterMs }: the engine serving it is restarting for a release, or the trader it serves moved to another engine, and the stream closes right after the event. code is engine_draining. Open the stream again after afterMs milliseconds, which is never more than 1,000: another engine serves it, and it opens as any connection does. It is not an error, so keep what you show until the new stream's first events arrive. The event sets the stream's retry: to afterMs too, so an EventSource that reconnects on its own follows it.
The response is of type string.