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:

RouteWho moves the dataBest for
Direct connectionTrX Journal reads the broker with a read-only key you add in the app.Tradier, Alpaca, OANDA, Kraken
Feed APIYour 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 importYou 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

  1. Sign in and open Brokers in the app.
  2. Choose your broker. The app shows which routes it supports and selects the best one.
  3. 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.
  4. Feed only: run GET /api/feed/v1/ping with the token to confirm it, then POST /api/feed/v1/fills with your fills in batches of up to 1000.
  5. 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_after in 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

MethodPathAuthPurpose
GET/api/feed/v1/pingTokenConfirm a token and see which connection it names.
POST/api/feed/v1/fillsTokenPush up to 1000 fills. Idempotent on external_id.
GET/api/feed/v1/fillsTokenRead the fills the connection holds, oldest first.
GET/api/feed/v1/brokersNoneThe 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.

Response 200
{
  "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.

Request
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" }
    ]
  }'
Response 200
{ "ok": true, "accepted": 2, "duplicates": 0, "total": 2 }
Response 422
{
  "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" }
  ]
}
StatusMeaning
200Stored. accepted = new fills, duplicates = already held, total = fills now on the connection.
400The body is not JSON.
401No token, a malformed token, or a token that was rotated or whose connection was removed.
403The account is not active, or its access term has ended (reading still works).
413The body is over 2 MB, or the connection would exceed 100,000 fills.
422One or more fills failed validation. Nothing was stored. See issues[].
429Over 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_TOKEN

GET /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

A fill
{
  "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"
}
FieldTypeRequiredNotes
external_idstring ≤ 128yesThe 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.
symbolstring ≤ 64yesUpper-cased on arrival. Use the broker’s symbol as is (AAPL, EUR_USD, XBTUSD, MESZ6).
sidebuy | sellyesAlso accepted: b, s, bought, sold, long, short, sell_short.
quantitynumber > 0yesAlways positive. Direction comes from side.
pricenumber ≥ 0yesFill price in the instrument’s quote currency.
feesnumber ≥ 0noCommission plus exchange and regulatory fees for this fill. Default 0.
executed_atISO-8601 or epochyesEpoch seconds or milliseconds are accepted. Stored as UTC. Alias: timestamp.
order_idstring ≤ 128noThe parent order, if the broker exposes it.
currencystring ≤ 12noAccount 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

push.mjs
// 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

push.py
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:

  1. 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.
  2. A bridge to the Feed API. A small Expert Advisor or script on the platform can read the deal history (HistoryDealsTotal and HistoryDealGetTicket in MQL5) and post each deal to /api/feed/v1/fills with the deal ticket as external_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].