List Trades
GET /api/v1/tradesScope: trades:read
Returns your 50 most recent trades, all statuses. Trades are scoped to the authenticated user — you'll never see another user's trades.
Response
200 OK
[
{
"trade_id": "00000000-0000-0000-0000-000000000000",
"user_id": "usr_YOUR_USER_ID",
"market": "HYPE",
"long_exchange": "lighter",
"long_wallet": "0x...",
"short_exchange": "extended",
"short_wallet": "0x...",
"target_spread": "0",
"spread_condition": "gte",
"target_size": "2",
"filled_size": "2",
"execution_type": "maker_taker",
"maker_leg": "long",
"max_slippage": "0.005",
"status": "completed",
"rate_budget": 20,
"created_at": "2026-03-12T13:06:58Z",
"updated_at": "2026-03-12T13:08:30Z",
"completed_at": "2026-03-12T13:08:30Z",
"size_type": "base",
"spread_type": "absolute",
"failure_reason": ""
}
]Trade object fields
The example above shows the base fields every trade carries. The fields below
are also returned and are committed: they keep their name and meaning
within /api/v1, and any change comes with a version bump. Fields marked
absent until are omitted (not null) before that point.
| Field | Type | Present | Meaning |
|---|---|---|---|
maker_filled_size | decimal string | after teardown | Gross size filled on the maker leg. filled_size keeps its meaning (gross maker fill); these four reconstruct the real position without a venue read |
hedge_filled_size | decimal string | after teardown | Size filled on the hedge leg |
flattened_size | decimal string | after teardown | Maker size closed reduce-only by a teardown corrective. On a trade with failure_code: "liquidation": what the flatten of the surviving leg executed, on whichever leg survived |
net_exposure | decimal string | after teardown | maker_filled_size − flattened_size − hedge_filled_size: > 0 unhedged maker exposure, < 0 over-hedged, 0 at a clean completion or when a maker fill was fully flattened. Non-zero on a terminal trade means a position is still open on a venue. On a trade with failure_code: "liquidation" the venue closed one leg, so this is what the survivor flatten left open on the surviving leg instead: > 0 on the maker leg, < 0 on the hedge leg. maker_filled_size and hedge_filled_size stay the fills before the liquidation |
working_order | object | GET /trades/{id} only, while a maker order is live | Metadata-only snapshot of the resting maker order, from the worker's last heartbeat: { "exchange", "market", "side": "buy"|"sell", "price", "size", "state": "pending"|"resting"|"filled"|"none" }. Carries no wallet or credentials. "none" means the marker was cleared. Lets a client tell "amend persisted" (price moved) from "quote re-priced" |
fills_hedged | boolean | when true | Every unit filled on the maker leg is covered by the hedge leg and no residual exposure remains: maker_filled_size > 0, flattened_size = 0, net_exposure = 0. Never on a trade with failure_code: "liquidation": the venue closed one leg, so it is not a hedged pair. The key is omitted when false, so it is an assertion and never a claim about a trade whose fill accounting was never written. Read it with status, not instead of it — see Reading a hedged partial |
failure_code | string | terminal trades | Typed terminal classification, derived at read time — see Failure codes |
failure_detail | string | terminal trades | Human detail behind failure_code; empty when the code says everything (a clean cancel) |
killed_by_switch | boolean | terminal trades, when true | The trade was halted by the user or global kill switch |
long_market_native | string | when it differs from market | The venue-native symbol the long leg trades (e.g. a venue that lists kBONK for BONK). Empty = same as market |
short_market_native | string | when it differs from market | Same for the short leg |
opened_at | RFC3339 timestamp | after the first maker fill | Position open time, stamped once by the worker on the first maker fill (created_at is when the trade was accepted) |
Provisional fields — returned today, read them as optional and expect the shape to change without a version bump:
| Field | Type | Meaning |
|---|---|---|
funding_at_entry | object | Both legs' live funding as the trade was admitted: { "stamped_at", "long": { "exchange", "annualised_pct", "source": "indicative"|"settled"|"unknown", "sampled_at", "next_funding_at" }, "short": { … }, "net_apr_pct" }. net_apr_pct is short minus long (positive = the position collects funding) and is present only when both legs had a reading. Absent when the cache had no reading — never a fabricated zero |
funding_at_exit | object | Same stamp taken as the trade went terminal |
hl_action_counts | object | Hyperliquid actions this trade consumed against the wallet's address budget: { "place", "modify", "cancel" } (integers). Hyperliquid's own nRequestsUsed increments the same way — one per order, one per modify, one per cancel, nothing for info requests — so these are the trade's real share of the address budget |
hl_action_budget | integer | The per-trade cap on Hyperliquid actions, for a trade whose maker leg is on Hyperliquid or trade[xyz]. While the trade runs it is what create asked for, if anything; when the trade ends it is the cap that applied (by default the trade's USD notional, at least 100). See Hyperliquid action cap |
fast_move_guard_stats | object | What the aggressive fast-move guard saw and did, written when the trade ends. Alongside triggers / cancels / holds it carries re_places and re_place_modifies: the placement that replaced each guard-cancelled order and the first modify that brought it to the touch. A trigger with a resting order therefore costs three actions in hl_action_counts — cancels + re_places + re_place_modifies is the guard's share of the burn |
unmatched_fills | array | Fills a worker saw on your account that it could not attribute to any order it placed: [{ "at", "reason": "unknown_cloid"|"tracked_mismatch", "exchange", "market", "cloid", "order_id", "side", "price", "size" }]. These are not this trade's fills — see Unclaimed fills on your account. Absent when there were none; capped at 20 per trade |
Reading a hedged partial
A trade that filled part of its target, hedged all of it, and then could not
quote the remainder ends failed — the venue rejected the last quote, so
the execution did fail — even though the position it holds is sound. The
example is a maker leg that filled 74% and left a $6 remainder under
Hyperliquid's $10 minimum: status: "failed", failure_code: "below_min_size", maker_filled_size and hedge_filled_size equal, and
net_exposure: "0".
Book from the fill fields, not from status: fills_hedged: true is the
single discriminator for this case, because net_exposure: "0" alone cannot
tell a hedged partial from a fill that a teardown corrective flattened away —
both read zero.
There is deliberately no separate terminal status for it. Clients switch on
status, so adding a value would be a breaking change dressed as an additive
one.
Unclaimed fills on your account
unmatched_fills is the one field on a trade that is not about that trade.
A worker streams the whole account it trades on, so it sees every fill on that account — including fills belonging to another of your trades, to another tool signing on the same wallet, or to an order nobody is tracking any more. When a fill arrives that the worker cannot attribute to an order it placed itself, it drops it. Dropping is correct: acting on a fill that may belong to something else would be worse than ignoring it.
What changed is that the drop is now recorded. The trade that happened to be
streaming the account when the fill landed carries it out as unmatched_fills,
because you are the only person who can say whose that fill was.
Read one as: something filled on your account and no Spreadr order claimed it. That is worth investigating — an unclaimed fill is an unhedged position until proven otherwise. It is not evidence that the reporting trade misbehaved, and the sizes in it must not be booked against that trade.
reason distinguishes two shapes, both meaning the same thing to you:
unknown_cloid— the fill arrived with no order state at all.tracked_mismatch— the fill arrived while a different order was being tracked.
The list is capped at 20 per trade. It tells you an unclaimed fill happened; it is not an exhaustive ledger.
Trade statuses
| Status | Description |
|---|---|
pending | Accepted, execution not yet started |
validating | Pre-flight checks (credentials, margin) in progress |
active | Trade is executing |
paused | Execution paused (e.g. during maintenance); position held |
recovering | Execution is being relaunched after a crash or disconnect |
completed | Target size fully filled |
canceled | Canceled by user |
rejected | Failed pre-flight validation (see failure_reason) |
failed | Execution failed (see failure_reason) |
Example
curl -H "X-API-Key: sprdr_..." https://api.spreadr.xyz/api/v1/tradesFilter client-side by status if you only want active ones:
trades = requests.get(f"{API_URL}/trades", headers=headers).json()
active = [t for t in trades if t["status"] in ("pending", "validating", "active", "paused", "recovering")]