API reference

Every SDK call, the fields it takes and returns, and the HTTP endpoints it uses on your behalf.

In development. Not released yet; names and formats may change.View as Markdown

Client methods

Python names first, then TypeScript. Every TypeScript method that talks to reTrade returns a promise.

The client’s methods
PythonTypeScriptWhat 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 blockrt.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)NoneCheck 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

The retrade command (npx retrade, for the TypeScript package)
CommandWhat it does
retrade keygen PATHWrite a new executor key, never over an existing file, and print its public half.
retrade claim-kit DECISION_ID --state-dir DIRPrint 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

An order’s fields
FieldPythonTypeScriptRules
InstrumentinstrumentinstrumentAn exact listing id, <MIC>:<symbol>, such as XNAS:AAPL; at most 128 characters.
SideSide.BUY, Side.SELL"buy", "sell"
QuantityquantityquantityA whole number, at least 1, at most 15 digits.
TypeOrderType.LIMIT, OrderType.MARKET"limit", "market"
Time in forceTif.DAY, Tif.IOC, Tif.GTC"day", "ioc", "gtc"
Limit pricelimit_pricelimitPriceRequired on a limit order, not allowed on a market order. Above 0.
Stop pricestop_pricestopPriceOptional: 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.

The account’s types and their fields
TypePython fieldsTypeScript fields
Snapshotas_of, cash, settled_cash, positions, open_orders, fx=None, adjustments=()asOf, cash, settledCash, positions, openOrders, fx?, adjustments?
Quoteinstrument, last, bid, ask, as_ofinstrument, last, bid, ask, asOf
Positioninstrument, quantity, markinstrument, quantity, mark
OpenOrderbroker_order_id, tag, instrument, side, remaining_qty, type, tif, limit_pricebrokerOrderId, tag, instrument, side, remainingQty, type, tif, limitPrice
Fxrate (USDCAD: CAD per 1 USD), as_ofrate, asOf
Adjustmentkind, amount, currency, instrument, quantitykind, amount, currency, instrument, quantity
ApprovedOrderinstrument, side, quantity, type, tif, limit_priceinstrument, side, quantity, type, tif, limitPrice
  • cash and settled_cash map a currency to an amount; a missing currency counts as 0.
  • Times are timezone-aware datetimes in Python, and Dates or RFC 3339 strings with an offset in TypeScript.
  • An open order’s tag is the broker’s client order id, or None (null) when it has none; its tif may be None when the broker doesn’t report it.
  • An adjustment’s kind is deposit, dividend, withdrawal, fee, transfer_in, transfer_out or split. Cash kinds carry an amount and a currency; transfers and splits carry an instrument and a quantity.

Result

What place returns
PythonTypeScriptWhat it holds
outcomeoutcomeALLOW, CUT or BLOCK: reTrade’s, or the SDK’s own when local is true.
placementplacementplaced, not_placed or unknown.
quantityquantityThe approved quantity, or what the SDK placed on its own. None for a BLOCK.
requested_quantityrequestedQuantityThe order’s own quantity.
decidingdecidingreTrade’s deciding rule, verbatim. None for an ALLOW and for every local result.
fitsfitsOn a BLOCK over a dollar line or in an agent conflict, the largest quantity that would pass.
flagsflagsEmpty: over a dollar line the answer is BLOCK.
alertsalertsThe decision’s alerts, passed through.
reasonreasonThe SDK’s own reason. See Reason codes.
decision_iddecisionIdThe decision’s id: the receipt id to store.
broker_order_idbrokerOrderIdThe broker’s id for the order, when one was placed.
locallocalThe SDK decided without a verified answer from reTrade.
claimableclaimableA 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.

reTrade’s endpoints
EndpointWhat it does
GET /healthzAnswers {"ok":true} when the service is up. The SDK doesn’t call it.
GET /v1/keysThe list of decision keys, signed by the root key: {"keys": "<compact JWS>"}.
GET /v1/statusThe latest signed log head and the signed status: the stops in force and each account’s settings.
POST /v1/decisionsAsk about one order. Answers {"decision": "<compact JWS>"}.
POST /v1/decisions/{decision_id}/submissionReport 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/stopsEngage a stop. Answers with the stop’s id and a signed receipt.
GET /v1/log?from=N&to=MYour log’s records, a page at a time.
POST /v1/snapshotsAn 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.

The five signed lines of a GET request
GET
/v1/log?from=1&to=2
2026-10-22T14:03:09Z

e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

In 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.

A request body, laid out for reading
{"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:

A decision’s payload, with its context left out
{"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.

The two kinds of report
{"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.