Documentation
Broker integration API
Everything you need to feed TrX Journal with fills from your broker: how the connections work, how to authenticate, each endpoint with its request and response, and a setup guide per broker.
Overview
TrX Journal journals fills: one execution of one order, in one direction, at one price. A broker connection delivers fills; the journal in your browser pairs them into round-trip trades and calculates everything from there. Three routes bring fills in:
| Route | Who moves the data | Best for |
|---|---|---|
| Direct connection | TrX Journal reads the broker with a read-only key you add in the app. | Tradier, Alpaca, OANDA, Kraken |
| Feed API | Your script, platform or bridge pushes fills to the Feed API with a per-connection token. | Any broker with an API, any platform that can run code, any export a bridge can read. |
| CSV import | You export a file and import it with the column mapper. | Every broker. Also the fallback when an API is down or has no history endpoint. |
Every route is read-only. No part of TrX Journal can place, change or cancel an order, and the setup guides ask for keys without trading permission.
How many connections a journal may hold at once depends on the tier: Tapestry 1, Studio 3, Atelier 10. Direct and feed connections both count; CSV import does not. Creating a connection past the allowance answers 409 and names the next tier, which upgrades the account when bought with the same email address.
Quick start
- Sign in and open Brokers in the app.
- Choose your broker. The app shows which routes it supports and selects the best one.
- Direct: paste the read-only key. The app tests it against the broker, stores it encrypted, and runs the first sync. Feed: the app mints a token. Copy it now; it is shown once.
- Feed only: run
GET /api/feed/v1/pingwith the token to confirm it, thenPOST /api/feed/v1/fillswith your fills in batches of up to 1000. - Back in the app, Pull into journal turns the stored fills into trades. The Brokers page does this on its own each time it opens.
Authentication
Feed endpoints take a bearer token in the Authorization header. A token starts with trxk_, belongs to exactly one connection, and through it to one user and one journal account. It can read and push fills for that connection and nothing else.
Authorization: Bearer trxk_YOUR_TOKEN- The token is shown once, when the connection is created or rotated. TrX Journal stores only its SHA-256 hash.
- Rotate it from the Brokers page at any time; the old token stops working immediately.
- Remove the connection and the token, its key and every fill it brought in are deleted. Trades already in your journal stay.
- Each token may make 120 requests a minute. A 429 answer carries
retry_afterin seconds. - Pushing needs a live licence. After the access term ends the token can still read fills but not add any.
The routes under /api/integrations/…are the app’s own, authenticated by the signed-in session cookie; a script does not call them.
Endpoints
| Method | Path | Auth | Purpose |
|---|---|---|---|
GET | /api/feed/v1/ping | Token | Confirm a token and see which connection it names. |
POST | /api/feed/v1/fills | Token | Push up to 1000 fills. Idempotent on external_id. |
GET | /api/feed/v1/fills | Token | Read the fills the connection holds, oldest first. |
GET | /api/feed/v1/brokers | None | The broker catalogue with routes per broker. |
GET /api/feed/v1/ping
The first call a script should make. Answers 200 with the connection the token names.
{
"ok": true,
"connection": {
"id": "bc_3f9a…",
"label": "Interactive Brokers",
"broker_id": "ibkr",
"broker": "Interactive Brokers (IBKR)",
"journal_account_id": "acct_4d1c2e9a",
"fills": 1240,
"last_fill_at": "2026-10-13T20:59:58.000Z"
},
"account": { "username": "jane.doe", "plan": "tapestry" },
"server_time": "2026-10-14T14:00:00.000Z"
}POST /api/feed/v1/fills
Body: { "fills": [ … ] } with 1 to 1000 fill objects (see the fill object). The whole batch is validated first; if any fill fails, nothing is stored and the answer is 422 listing the first ten problems. On success every fill not already held is stored, and the answer says how many were new and how many were repeats.
curl -X POST https://trxjournal.com/api/feed/v1/fills \
-H "Authorization: Bearer trxk_YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"fills": [
{ "external_id": "exec-7781203", "symbol": "AAPL", "side": "buy",
"quantity": 100, "price": 187.42, "fees": 0.35,
"executed_at": "2026-10-14T13:45:12Z" },
{ "external_id": "exec-7781377", "symbol": "AAPL", "side": "sell",
"quantity": 100, "price": 188.10, "fees": 0.35,
"executed_at": "2026-10-14T15:02:40Z" }
]
}'{ "ok": true, "accepted": 2, "duplicates": 0, "total": 2 }{
"error": "Some fills did not validate. Nothing was stored.",
"issues": [
{ "path": "fills.3.side", "message": "side must be buy or sell" },
{ "path": "fills.7.executed_at", "message": "executed_at is required" }
]
}| Status | Meaning |
|---|---|
200 | Stored. accepted = new fills, duplicates = already held, total = fills now on the connection. |
400 | The body is not JSON. |
401 | No token, a malformed token, or a token that was rotated or whose connection was removed. |
403 | The account is not active, or its access term has ended (reading still works). |
413 | The body is over 2 MB, or the connection would exceed 100,000 fills. |
422 | One or more fills failed validation. Nothing was stored. See issues[]. |
429 | Over 120 requests a minute for this token. Wait retry_after seconds. |
GET /api/feed/v1/fills
Query: since (ISO-8601, optional, fills executed strictly after it) and limit (default 1000, max 20,000). Fills come back oldest first in the same shape they were sent, plus received_at.
GET https://trxjournal.com/api/feed/v1/fills?since=2026-10-01T00:00:00Z&limit=500
Authorization: Bearer trxk_YOUR_TOKENGET /api/feed/v1/brokers
Public. The same list as the Integrations page, as JSON, with the routes and the broker-side source per broker.
The fill object
{
"external_id": "exec-7781203",
"symbol": "AAPL",
"side": "buy",
"quantity": 100,
"price": 187.42,
"fees": 0.35,
"executed_at": "2026-10-14T13:45:12Z",
"order_id": "ord-5519",
"currency": "USD"
}| Field | Type | Required | Notes |
|---|---|---|---|
external_id | string ≤ 128 | yes | The broker’s own id for the execution. Sending the same id again is a no-op, so re-running a script is safe. If the broker gives none, derive one from time + symbol + quantity + price. |
symbol | string ≤ 64 | yes | Upper-cased on arrival. Use the broker’s symbol as is (AAPL, EUR_USD, XBTUSD, MESZ6). |
side | buy | sell | yes | Also accepted: b, s, bought, sold, long, short, sell_short. |
quantity | number > 0 | yes | Always positive. Direction comes from side. |
price | number ≥ 0 | yes | Fill price in the instrument’s quote currency. |
fees | number ≥ 0 | no | Commission plus exchange and regulatory fees for this fill. Default 0. |
executed_at | ISO-8601 or epoch | yes | Epoch seconds or milliseconds are accepted. Stored as UTC. Alias: timestamp. |
order_id | string ≤ 128 | no | The parent order, if the broker exposes it. |
currency | string ≤ 12 | no | Account currency of the fill, for your records. No conversion is performed. |
camelCase aliases (externalId, executedAt, orderId) are accepted so a connector payload can be forwarded unchanged.
How fills become trades
The server stores fills and nothing more. When the Brokers page opens, or when you press Pull into journal, the browser fetches every fill a connection holds, groups them by symbol, sorts them by time, and matches opening fills against closing fills first-in, first-out. A round trip closes when the position returns to zero; partial fills are carried. The result is the same trade object a CSV import produces, in the journal account the connection feeds.
- Trade ids are derived from the fills, so pulling twice never duplicates a trade, and notes, tags and screenshots you add to a trade survive the next pull.
- A position still open at the newest fill is reported as open and becomes a trade once its closing fill arrives.
- Fees on a trade are the sum of fees on the fills that made it.
- Stops and R-multiples are never invented. Add a stop to a trade and its R is calculated from it.
Examples
Node
// node 18+, no dependencies
const TOKEN = process.env.TRX_TOKEN; // trxk_…
const fills = await readFillsFromMyBroker(); // your side: API, export, platform log
for (let i = 0; i < fills.length; i += 1000) {
const batch = fills.slice(i, i + 1000).map((f) => ({
external_id: f.id,
symbol: f.symbol,
side: f.qty > 0 ? "buy" : "sell",
quantity: Math.abs(f.qty),
price: f.price,
fees: f.commission ?? 0,
executed_at: f.time, // ISO-8601 or epoch
}));
const res = await fetch("https://trxjournal.com/api/feed/v1/fills", {
method: "POST",
headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify({ fills: batch }),
});
console.log(res.status, await res.json()); // { ok, accepted, duplicates, total }
}Python
import os, requests
TOKEN = os.environ["TRX_TOKEN"] # trxk_…
fills = read_fills_from_my_broker() # your side
for i in range(0, len(fills), 1000):
batch = [{
"external_id": f["id"],
"symbol": f["symbol"],
"side": "buy" if f["qty"] > 0 else "sell",
"quantity": abs(f["qty"]),
"price": f["price"],
"fees": f.get("commission", 0),
"executed_at": f["time"], # ISO-8601 or epoch
} for f in fills[i:i + 1000]]
r = requests.post("https://trxjournal.com/api/feed/v1/fills",
headers={"Authorization": f"Bearer {TOKEN}"},
json={"fills": batch}, timeout=30)
print(r.status_code, r.json())Run the script on a schedule (cron, Task Scheduler, a platform timer). Because external_id makes every push idempotent, the simplest reliable pattern is to send the last few days of fills on every run and let TrX Journal discard the repeats.
Broker setup guides
Each broker on the Integrationspage falls into one of the groups below. Menu names and permission labels are the broker’s own and change from time to time; the shape of each setup does not.
Direct connections
Done entirely inside the app: open Brokers, choose the broker, paste the key, done. The app tests the key before storing it and syncs on its own whenever the page opens, or on demand.
- Alpaca — in the dashboard open API Keys and generate a key pair for the account to journal. Paste the key id and secret and choose Live or Paper. Fills come from the account activities (FILL) endpoint, one row per execution.
- Tradier — in the dashboard open API Access for the access token, then copy the account number. Choose Live or Sandbox. Fills come from the account history endpoint filtered to trades.
- OANDA — in your account open Manage API Access and generate a personal access token. Paste it with the account id (the 001-001-1234567-001 form) and choose fxTrade or fxTrade Practice. Fills are ORDER_FILL transactions from the v20 API; later syncs continue from the last transaction id.
- Kraken — in Settings open API and create a key with only Query Funds and Query Closed Orders & Trades; leave every trade and withdrawal permission off. Paste the API key and private key. Fills come from TradesHistory, fifty a page.
Brokers with a public API, through the Feed API
These brokers publish an API that returns executions or trade history. The pattern is the same for all of them: create a read-only key or complete the OAuth flow on your side, read the fills with a small script, map them to the fill object, and push them with your connection’s token. Most of these APIs page by date or by id; send the last few days on every run and rely on external_id to drop repeats.
- Interactive Brokers (IBKR) · Global · Client Portal API, Flex Queries or Activity Statements
- Charles Schwab · US · Schwab Trader API or transaction history export
- E*TRADE (Morgan Stanley) · US · E*TRADE API or transaction history export
- Saxo Bank · Global · Saxo OpenAPI or trade report export
- Webull · US / Global · Webull OpenAPI or order history export
- Trading 212 · UK / Europe · Trading 212 public API or history export
- Moomoo (Futu Holdings) · US / Asia / Australia · moomoo OpenAPI or order history export
- tastytrade · US · tastytrade Open API or transaction history export
- TradeStation · US · TradeStation WebAPI or platform export
- Questrade · Canada · Questrade API or activity export
- Zerodha · India · Kite Connect API or Console tradebook export
- Angel One · India · SmartAPI or tradebook export
- Tradovate · US · Tradovate API or fills export
- Capital.com · Global · Capital.com API or trading history export
- XTB · Europe / Global · XTB xAPI or xStation history export
- IG Group · UK / Global · IG REST API or transaction history export
- FXCM · Global · FXCM REST API where offered, or MetaTrader 4 history
- Binance · Global · Binance API, read-only key, or trade history export
- Coinbase · US / Global · Coinbase Advanced Trade API or transaction history export
- Bybit · Global · Bybit v5 API, read-only key, or trade history export
- OKX · Global · OKX v5 API, read-only key, or trade history export
OAuth brokers (Schwab, E*TRADE, Saxo, TradeStation, Questrade, Zerodha, Angel One) issue short-lived access tokens with a refresh token; keep the refresh loop on your side and send only fills to TrX Journal. Crypto exchanges report fills per market; collect across the markets you traded and use the exchange’s trade id as external_id.
MetaTrader and cTrader brokers
Two ways, both read-only:
- Export and import. In MetaTrader 4 open the Account History tab, right-click and save the report (HTML or CSV). In MetaTrader 5 open History, right-click, Report, then Open XML or HTML. In cTrader open History and export. Import the file with the column mapper; the deal ticket is the execution id, and the mapper remembers the layout.
- A bridge to the Feed API. A small Expert Advisor or script on the platform can read the deal history (
HistoryDealsTotalandHistoryDealGetTicketin MQL5) and post each deal to/api/feed/v1/fillswith the deal ticket asexternal_id. Allow the request URL in the platform’s WebRequest settings. cTrader Automate can do the same in C#.
- Pepperstone · Australia / UK / Global · MetaTrader 4/5 account history, or cTrader where offered
- IC Markets · Australia / Global · MetaTrader 4/5 account history, or cTrader where offered
- XM Group · Global · MetaTrader 4/5 account history, or cTrader where offered
- FxPro · UK / Global · MetaTrader 4/5 account history, or cTrader where offered
- AvaTrade · Global · MetaTrader 4/5 account history, or cTrader where offered
- FP Markets · Australia / Global · MetaTrader 4/5 account history, or cTrader where offered
- Fusion Markets · Australia / Global · MetaTrader 4/5 account history, or cTrader where offered
- Tickmill · UK / Global · MetaTrader 4/5 account history, or cTrader where offered
- Exness · Global · MetaTrader 4/5 account history, or cTrader where offered
- Axi · Australia / Global · MetaTrader 4/5 account history, or cTrader where offered
- Admiral Markets (Admirals) · Europe / Global · MetaTrader 4/5 account history, or cTrader where offered
Futures platforms
Futures brokers route through a trading platform, and the platform holds the executions. Export from the platform (NinjaTrader: Control Center, Account Performance or Executions, export; Rithmic, CQG and similar: the orders and fills report in the trading platform) and import the file, or run a platform add-on that posts fills to the Feed API as they happen. Tradovate also publishes a REST API that returns fills directly.
- NinjaTrader · US / Global · NinjaTrader platform executions export
- Stage 5 Trading · US · Platform fills export (Rithmic, CQG and others)
- AMP Futures · US / Global · Platform fills export (Rithmic, CQG, MetaTrader 5 and others)
Brokers best served by export
These brokers offer no public trade API today, or none that returns executions. Export the transaction or activity history as CSV from the account’s statements or history area and import it; the column mapper handles each broker’s layout and remembers it. A statement that only comes as PDF needs converting to CSV first. If you keep a bridge that reads the export, it can push the same rows through the Feed API instead.
- Fidelity Investments · US · Activity and orders history export
- Robinhood · US · Account statements and trade confirmations
- eToro · Global · Account statement export
- Trade Republic · Europe · Transaction statements
- Public.com · US · Account statements and history export
- Lightyear · UK / Europe · Transaction history export
- Wealthsimple · Canada · Activity and statements export
- Hargreaves Lansdown · UK · Transaction history export
- AJ Bell · UK · Transaction history export
- Interactive Investor · UK · Transaction history export
- DEGIRO · Europe · Transactions export
- Swissquote · Switzerland · Transaction statements export
- Groww · India · Order history and statements export
- Stage 5 Trading · US · Platform fills export (Rithmic, CQG and others)
- AMP Futures · US / Global · Platform fills export (Rithmic, CQG, MetaTrader 5 and others)
- Plus500 · Global · Closed positions export
- CMC Markets · UK / Australia / Global · Account history export
Categories covered: Stocks, ETFs and options, Futures, Forex and CFDs, Crypto. 54 brokers in all.
Security
- Read-only by construction. The connectors implement reads only. There is no code path that places, changes or cancels an order or moves money.
- Least privilege. Create broker keys without trading or withdrawal permission. A key that can only read is all a connection needs.
- Encrypted at rest. Broker keys are stored AES-256-GCM encrypted. Feed tokens are stored only as a SHA-256 hash and shown once.
- Scoped tokens. A feed token reaches one connection’s fills and nothing else: not your journal, not your account, not another connection.
- Revocable. Rotate a token or remove a connection at any time. Removal deletes the key and the stored fills.
- Your records stay yours. Trades already in the journal are never removed by a connection change, and everything exports as CSV or JSON.
Questions about an integration: [email protected].