API reference
Every SDK call, the fields it takes and returns, and the HTTP endpoints it uses on your behalf.
Client methods
Python names first, then TypeScript. Every TypeScript method that talks to reTrade returns a promise.
| Python | TypeScript | What it does |
|---|---|---|
retrade.Client(key_id, key_path, *, root_key, base_url=None, state_dir="retrade-state") | Client.open({ keyId, keyPath, rootKey, baseUrl, stateDir, logger }) | Load the key, open the claim kit, and fetch and check the key list and the status. |
rt.place(order, *, account, agent, via) | rt.place(order, { account, agent, via }) | Check one order and act on the answer. Returns a Result. |
rt.replace(broker_order_id, order, *, account, agent, via) | rt.replace(brokerOrderId, order, { account, agent, via }) | Cancel a working order, then decide on the new one. If the cancel fails, nothing new is placed. |
rt.stop(scope, *, account=None, agent=None, reason) | rt.stop(scope, { account, agent, reason }) | Engage a stop for the tenant, an account or an agent. Returns the stop id. |
rt.cancel_open_orders(via, *, all_orders=False) | rt.cancelOpenOrders(via, { allOrders }) | Cancel the working orders that carry a reTrade tag, or all of them. Returns the ids cancelled. |
rt.send_pending_reports() | rt.sendPendingReports() | Resend submission reports that didn’t reach reTrade. Returns how many went. |
rt.refresh() | rt.refresh() | Fetch and check the key list and the status again. |
rt.log(*, from_seq=None, to_seq=None) | rt.log({ from, to }) | Read your log, each record checked and chained. Returns a list of LogRecords. |
rt.claim_kit(decision_id) | rt.claimKit(decisionId) | One decision’s evidence, or None (null). |
rt.send_snapshot(account, via) | rt.sendSnapshot(account, via) | Send an account’s snapshot with no order. Not built on the hosted service yet. |
rt.keep_fresh(adapters, *, every_s=300) | rt.keepFresh(adapters, { everyMs }) | Every 5 minutes until close, refresh the status and send each account’s snapshot. Snapshots aren’t built on the hosted service yet. |
rt.close(), or a with block | rt.close() | Stop background work and close the connection and the claim kit. |
retrade.generate_key(path) | generateKey(path) | Write a new executor key and return its public half. |
retrade.load_key(path) | loadKey(path) | Load an executor key file. |
retrade.verify_webhook(secret, timestamp, body, signature) | None | Check a webhook in its planned format. reTrade doesn’t send webhooks yet. |
rt.mode is the client’s mode, and rt.base_url (rt.baseUrl) the address it talks to. DEFAULT_BASE_URL is https://api.retrade.app.
The command line
| Command | What it does |
|---|---|
retrade keygen PATH | Write a new executor key, never over an existing file, and print its public half. |
retrade claim-kit DECISION_ID --state-dir DIR | Print one decision’s evidence as JSON. DIR is retrade-state by default. |
Your admins have their own command line, retrade-admin, for limits, modes, keys and stops. It’s in development.
Orders
| Field | Python | TypeScript | Rules |
|---|---|---|---|
| Instrument | instrument | instrument | An exact listing id, <MIC>:<symbol>, such as XNAS:AAPL; at most 128 characters. |
| Side | Side.BUY, Side.SELL | "buy", "sell" | |
| Quantity | quantity | quantity | A whole number, at least 1, at most 15 digits. |
| Type | OrderType.LIMIT, OrderType.MARKET | "limit", "market" | |
| Time in force | Tif.DAY, Tif.IOC, Tif.GTC | "day", "ioc", "gtc" | |
| Limit price | limit_price | limitPrice | Required on a limit order, not allowed on a market order. Above 0. |
| Stop price | stop_price | stopPrice | Optional: the exit level written_exit reads. Above 0. Not sent to the broker. |
Amounts are exact: Decimal or int in Python, decimal strings or bigints in TypeScript, never floats or JavaScript numbers. At most 15 digits before the point and 10 after it. In Python the enums’ plain strings ("buy") work too.
Account data
What your adapter’s snapshot function returns, and what submit is given.
| Type | Python fields | TypeScript fields |
|---|---|---|
Snapshot | as_of, cash, settled_cash, positions, open_orders, fx=None, adjustments=() | asOf, cash, settledCash, positions, openOrders, fx?, adjustments? |
Quote | instrument, last, bid, ask, as_of | instrument, last, bid, ask, asOf |
Position | instrument, quantity, mark | instrument, quantity, mark |
OpenOrder | broker_order_id, tag, instrument, side, remaining_qty, type, tif, limit_price | brokerOrderId, tag, instrument, side, remainingQty, type, tif, limitPrice |
Fx | rate (USDCAD: CAD per 1 USD), as_of | rate, asOf |
Adjustment | kind, amount, currency, instrument, quantity | kind, amount, currency, instrument, quantity |
ApprovedOrder | instrument, side, quantity, type, tif, limit_price | instrument, side, quantity, type, tif, limitPrice |
cashandsettled_cashmap a currency to an amount; a missing currency counts as 0.- Times are timezone-aware
datetimes in Python, andDates or RFC 3339 strings with an offset in TypeScript. - An open order’s
tagis the broker’s client order id, orNone(null) when it has none; itstifmay beNonewhen the broker doesn’t report it. - An adjustment’s
kindisdeposit,dividend,withdrawal,fee,transfer_in,transfer_outorsplit. Cash kinds carry an amount and a currency; transfers and splits carry an instrument and a quantity.
Result
| Python | TypeScript | What it holds |
|---|---|---|
outcome | outcome | ALLOW, CUT or BLOCK: reTrade’s, or the SDK’s own when local is true. |
placement | placement | placed, not_placed or unknown. |
quantity | quantity | The approved quantity, or what the SDK placed on its own. None for a BLOCK. |
requested_quantity | requestedQuantity | The order’s own quantity. |
deciding | deciding | reTrade’s deciding rule, verbatim. None for an ALLOW and for every local result. |
fits | fits | On a BLOCK over a dollar line or in an agent conflict, the largest quantity that would pass. |
flags | flags | Empty: over a dollar line the answer is BLOCK. |
alerts | alerts | The decision’s alerts, passed through. |
reason | reason | The SDK’s own reason. See Reason codes. |
decision_id | decisionId | The decision’s id: the receipt id to store. |
broker_order_id | brokerOrderId | The broker’s id for the order, when one was placed. |
local | local | The SDK decided without a verified answer from reTrade. |
claimable | claimable | A placed enforce-mode decision: evidence an insurer can review. It promises no payment. |
Python quantities are int; TypeScript quantities are strings, as on the wire.
HTTP endpoints
The SDK calls these for you, at https://api.retrade.app by default. It signs every request; GET /v1/keys and GET /healthz also answer unsigned ones.
| Endpoint | What it does |
|---|---|
GET /healthz | Answers {"ok":true} when the service is up. The SDK doesn’t call it. |
GET /v1/keys | The list of decision keys, signed by the root key: {"keys": "<compact JWS>"}. |
GET /v1/status | The latest signed log head and the signed status: the stops in force and each account’s settings. |
POST /v1/decisions | Ask about one order. Answers {"decision": "<compact JWS>"}. |
POST /v1/decisions/{decision_id}/submission | Report what happened at the broker. Answers with a signed receipt. |
GET /v1/policies/{policy_hash} | The rules document a decision was made under. |
POST /v1/stops | Engage a stop. Answers with the stop’s id and a signed receipt. |
GET /v1/log?from=N&to=M | Your log’s records, a page at a time. |
POST /v1/snapshots | An account’s snapshot with no order. Not built yet: it answers 404. |
Request signing
Every signed request carries Retrade-Key-Id, Retrade-Timestamp, an Idempotency-Key on a POST, and Retrade-Signature: base64url, without padding, of an Ed25519 signature over five lines. reTrade accepts a timestamp within 60 seconds of its own clock.
GET
/v1/log?from=1&to=2
2026-10-22T14:03:09Z
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855In order: the method, the full path with its query, the timestamp, the idempotency key (empty for a GET) and the lowercase hex SHA-256 of the body (here of an empty body). The same idempotency key with the same body gets the stored answer back, byte for byte; with a different body it’s refused.
The decision request
POST /v1/decisions carries the order, a quote and the account data your adapter read, in one JSON body. Numbers are strings, so nothing is rounded on the way. The SDK keeps these exact bytes, because reTrade’s answer is bound to their SHA-256.
{"account_ref": "A1",
"agent_id": "agt_m",
"order": {"instrument": "XNAS:AAPL", "side": "buy", "quantity": "40",
"type": "limit", "limit_price": "187.25", "tif": "day",
"intent": "increase"},
"quote": {"instrument": "XNAS:AAPL", "last": "187.10", "bid": "187.08",
"ask": "187.12", "as_of": "2026-10-22T14:03:06.415Z"},
"snapshot": {"as_of": "2026-10-22T14:03:05.415Z",
"cash": {"USD": "38380.90"},
"settled_cash": {"USD": "38380.90"},
"positions": [
{"instrument": "XNAS:AAPL", "quantity": "81", "mark": "187.10"},
{"instrument": "ARCX:VTI", "quantity": "160", "mark": "290.40"}],
"open_orders": []}}The SDK works out intent itself: a buy is open when the account holds none of the stock and increase otherwise, and a sell is reduce. A stop price goes as "exit": {"stop_price": …}. There’s no approval field: nothing is ever approved by a person.
The signed decision
reTrade answers {"decision": "<compact JWS>"}, signed with Ed25519 by the day’s decision key. Its header is alg EdDSA, a kid and typ retrade-decision. The payload:
{"decision_id": "01JB8Z6Q4M3T9V2K7X5R1C0N8D", "tenant_id": "T1",
"mode": "enforce", "account_ref": "A1", "agent_id": "agt_m",
"outcome": "BLOCK", "quantity": null,
"deciding": {"code": "approval_line", "reason": "per_order"},
"fired": [{"code": "approval_line", "reason": "per_order"},
{"code": "approval_line", "reason": "per_day"}],
"rooms": {"cash_funding": "204"}, "fits": "26",
"opening": false, "p": "187.25", "line_value": "7490", "expires": null,
"accepts": {"snapshot": true, "mark": true, "rate": false},
"switch_off_agent": false, "links": [], "alerts": [], "value": "100000",
"requested_quantity": "40",
"order": {"instrument": "XNAS:AAPL", "side": "buy", "type": "limit",
"tif": "day", "limit_price": "187.25"},
"request_sha256":
"a7d194e7a02eebf02e40d2eaaeb6c5d4b9619cab31913abbb2b765ecf2303fbc",
"policy_hash":
"5639cfbad8cb84092c15c01832cd535606538cde4f9c798ca1da43df4f4adf07",
"policy_epoch": "1",
"master_sha256":
"a58c17ac155efdc751b4f028db283d14e889a67425ed21ebd2b15c7e25078f32",
"engine_version": "retrade-engine/0.2.0",
"spec_version": "retrade-arith/2.0-draft",
"evaluated_at": "2026-10-22T14:03:07.415Z", "seq": "1",
"prev_hash":
"0000000000000000000000000000000000000000000000000000000000000000",
"claimable": true}An ALLOW or CUT carries quantity and expires, the end of its life: 30 seconds by default, and an admin can set it from 5 to 60. A BLOCK has neither. The full payload also carries context, the data reTrade decided on. claimable is true for an enforce decision: it says the decision is evidence an insurer can review, and promises no payment. The SDK ignores members it doesn’t know.
The submission report
After the broker call, the SDK sends POST /v1/decisions/{decision_id}/submission with one of two bodies, and the decision id as its idempotency key. reTrade answers with a signed receipt.
{"kind": "placed", "broker_order_id": "...",
"submitted_at": "<time of the broker call>"}
{"kind": "not_submitted", "reason": "rejected" | "withdrawn"}placed: the broker took the order. Its id links the order to its decision where the broker drops the tag.rejected: the broker definitely refused it.withdrawn: the SDK didn’t place it, because time ran out or a stop is engaged.
A report goes only for a verified ALLOW or CUT, once. A timeout or an unknown outcome is never reported as not placed: the adapter looks the order up instead, and if it still can’t tell, the decision keeps counting against the limits until the account data shows the order or its absence.
Broker tags
The SDK puts a tag in the broker’s client order id: rt1- in enforce mode, rs1- in shadow and rx1- in test, then the decision id, a ULID. That’s 30 characters; a broker whose client order id holds fewer would need a shorter form.