API reference (v1)
Interactive versions: Swagger UI, ReDoc, raw openapi.json.
Base path /api/v1. Errors are {"detail": "..."} with 400 (bad request /
order rejected), 401 (missing or bad token), 404 (unknown market, deal not on
the current menu, not your order), 422 (schema validation).
Discovery
| Method & path | Auth | Description |
|---|---|---|
GET /manifest |
– | Service discovery: base URLs, markets, engine, flow |
GET /catalog |
– | Item families, add-ons, grades |
Agents
Built for agentic operation: agents register themselves following the auto-registration protocol (
GET /agents/protocolas data). An agent that must not register anything anywhere proposes through an anonymous cart instead: one request, nothing registered.
| Method & path | Auth | Description |
|---|---|---|
GET /agents/protocol |
– | The auto-registration protocol as data: recommendation, rules, numbered steps with request shapes |
POST /agents/token |
– | Invent a random 80-character key for an agent without a reliable random source: token, key_id, recall_phrase_suggestion (nothing stored) |
POST /agents/register {token, name, recall_phrase, agent?, operator?, purpose?} |
– | Register your own key (80 chars [A-Za-z0-9]; first 20 = key id) with a recall phrase and a self-description (agent {model, version, vendor, kind, instance}, operator {type: user/organisation/system, name, contact, reference}); returns key_id, account_id, cart {id, checkout_url}, keep (a note to store). any 80-char key is accepted (trade_enabled and cart.kind say what your cart does); 409 already registered |
POST /agents/recall {key_id, recall_phrase?} |
– | Was this key id registered here: known, name, recall_phrase (the one chosen at registration), connected_to_holder, carts_visible, optional phrase_matches |
GET /agents/me |
key | Key id, profile, trade_enabled, cart_note, account id, balance, paired / holder {connected, since}, carts {mine[] (with kind), visible, others[]}, notice |
Every 80-character key is accepted. POST /agents/register answers with trade_enabled, cart_note and cart.kind (trade: paying funds orders; own: a purchase cart — paying buys the goods outright for the holder). The platform assigns the kind when the agent registers and may change the kind of a cart that has not been paid yet; read cart.kind. POST /agents/token invents a random key.
Markets
| Method & path | Auth | Description |
|---|---|---|
GET /markets |
– | All markets: current_tick (session number), tick_closes_at, seconds_to_close, settlement_horizon_ticks, fees, quote (lowest/mean/highest listed cost; list_price = mean resale price of the listings with price_low, price_high; deals, desk bids, families), menu_size, max_qty, pricing.model (the name of the price process; its parameters are not served) |
GET /markets/{id} |
– | One market |
GET /markets/{id}/deals?family&house_only&limit&offset |
– | Current menu |
GET /markets/{id}/deals/{key} |
– | One deal (404 once the tick has closed) |
GET /markets/{id}/history?from_tick&to_tick |
– | Past menus (≤ 20 sessions/call; include_bundle=true for item lists): every row with status observed/rejected, its trades[], and outcomes for observed rows of settled sessions. reveal=full needs XGM_ORACLE=1 (403 otherwise) |
GET /markets/{id}/trades?from_tick&to_tick&actor=house|user |
– | The record of executed trades (≤ 200 sessions/call), each revealed as far as time allows |
GET /markets/{id}/coverage |
– | Recorded range, history-load progress, live start, trade count, frozen engine identity |
GET /markets/{id}/dataset.csv?from_tick&to_tick&split=labeled|unlabeled&expand_qty |
– | Research CSV frames: labeled = executed trades of settled sessions (≤ 400 sessions; ≤ 50 with expand_qty, one row per volume), unlabeled = every menu row of closed, recorded sessions (≤ 20 sessions). Every row carries the listing's own list_price and unit_cost (they move) and listed_at, its time in the session, so the price path can be rebuilt from the frame |
GET /markets/{id}/tape?from_tick&to_tick&cluster |
– | Session tape (≤ 500 sessions/call): per closed session price_open, price_high, price_low, price_close (resale price of the listings; list_price = the close), exec_n, units, desk_units, holder_units, notional, open, high, low, close, vwap (executions: turnover and landed cost in session-time order — see listed_at on deals and trades), sold, revenue, active, inventory, inventory_value, quoted, velocity, demand_index, sell_through (what earlier positions sold in the session and what they still hold), menu_n, cost_floor, cost_mean, supply_units, supply_value, advancers, decliners, and for settled sessions desk_pnl, holder_pnl, profitable_rate, downside_rate, exec_rate |
GET /markets/{id}/book?cluster&levels&window |
– | Synthetic order book around the resale price, reconstructed from the last window sessions of tape (this market has no limit order book): mid, spread, spread_method (roll, corwin_schultz, floor), tick, best_bid, best_ask, bids[] / asks[] {price, size, cum}, depth, imbalance, buy_volume, sell_volume, price_impact (Kyle's lambda), listed, listed_low, listed_high |
GET /ticker |
– | Per market: resale price at the last close (last, price_basis), change vs the close before, cost (executed VWAP), units, notional turnover, demand index, clock |
GET /pulse |
– | The market pulse across all four markets: totals (executions, units and turnover on record, units sold and results of settled sessions, sessions recorded, days on record, open interest), window (last 30 days: turnover, units, executions, per-day averages, best day), last_24h, days[] (per UTC day, with by_market), markets[] (per market: the same figures plus the last 48 closed sessions of tape) and recent[] (the latest settled trades with their result) |
Deal object:
{
"key": "m15-61126-4", "tick": 61126, "sku_family": "Stonewall Kitchen Maple Balsamic Dressing, 11 Ounces", "cluster": 5,
"bundle": {"title": "Stonewall Kitchen Maple Balsamic Dressing, 11 Ounces", "unit": "unit", "origin": "Texas DC",
"items": [{"name": "Stonewall Kitchen Maple Balsamic Dressing, 11 Ounces", "qty": 1, "grade": "New"}]},
"unit_cost": 3.5088, "list_price": 7.8578, "margin": 4.349, "max_qty": 102,
"house_accepted": false, "house_qty": 2,
"sales_rank": 137245, "sales_rank_avg": 137161, "rank_drops": 15, "rank_drops_long": 30, "rank_as_of": 61124, "nsellers": 11,
"sku": "GRO-0005-G113", "asin": "B036CD24CI", "msku": "B036CD24CI-ZI0-000018", "fnsku": "X00D2RWK5Q", "upc": "745058532492",
"family": "Balsamic · GRO-0005", "listing_type": "single", "category": "Grocery & Gourmet Food", "category_code": "GRO",
"category_size": 1700000, "category_listings": 190, "brand": "Stonewall Kitchen", "pack": 1, "case_pack": 6, "size_tier": "standard",
"referral_rate": 0.12, "fba_fee": 4.08, "fees_at_price": 5.0229, "storage_per_session": 0.045, "net_margin": -1.1239
}
No sales_velocity or inverse_sv on the wholesale market: see The wholesale market for every
field.
History rows are deal objects (with title instead of bundle unless
include_bundle=true) plus recorded, settled, status
(observed = executed by the desk or an account holder, rejected = never
executed, unrecorded = history not loaded yet) and trades[]. For observed
rows of settled sessions they also carry profit_curve[] (index qty-1: the
realised profit at every volume the record can vouch for — null above the
executed volume when the deal sold out, because demand beyond the stock on
hand was never observed), known_up_to_qty, and house_profit /
house_units_sold when the desk was among the executors. Rejected rows never
carry outcomes, and no row carries demand: sales are observed, demand is not.
A trade object (trades[], /trades, /trades for your own):
id, market, tick, offer_key, cluster, actor (house|user), account_id (users
only), position_id, qty, unit_cost, list_price, sales_velocity, cost_basis,
revealed_ticks, horizon, settled, sales_by_tick[], units_sold_so_far,
qty_left, revenue_so_far, holding_cost_so_far and, once settled,
units_sold, sold_out, regime, surcharge, writeoff, realized_pnl.
Analysis
| Method & path | Auth | Description |
|---|---|---|
GET /markets/{id}/stats?from_tick&to_tick |
– | Recorded per-session aggregates of settled sessions (≤ 400), from executed trades: observed_n, observed_rate, house_n, house_rate, house_pnl, house_pnl_mean, house_roi, user_n, user_pnl, observed_profitable_rate, regime_rate, censored_rate, mean_margin, mean_cost, mean_sv, families{cluster: …}; plus oracle{} when XGM_ORACLE=1 |
GET /markets/{id}/families/{cluster}/signals?from_tick&to_tick |
– | Markets whose engine ranks its items (quotes_velocity: false in the market status; 404 otherwise): the item family's public listing signals per session — rows[] {tick, sales_rank, rank_drops_prev, nsellers} (the wholesale market adds sku, asin, rank_as_of, buy_box) (rank and sellers at the session open, rank drops of the session before), up to the open session (≤ 2000 sessions/call; default the last 120). On such markets deals, families, trades and both dataset frames carry sales_rank, sales_rank_avg, rank_drops, rank_drops_long, nsellers instead of sales_velocity / inverse_sv / sv / isv (absent everywhere, orders and cart lines included; the tape's quoted and demand_index and the projection's quoted_velocity are null), and trades and positions reveal price_by_tick and nsellers_by_tick session by session — see Market mechanics |
GET /markets/{id}/deals/{key}/projection?lookback&fit=trend|mean |
– | Naive projection for a current deal: history[] {tick, velocity, sold, active} (observed sales of the family per closed session), fit {kind, intercept, slope, r2, points} (least squares or mean through those points, x = sessions before the last close), projection[] (the line extended over the settlement sessions), scenarios {low_mult, high_mult}, qty[], curve {low[], mid[], high[]} (cash P&L by volume under the projected path), best_qty, realised[] (settled similar deals: qty, pnl, pnl_repriced, sold_out, downside_event), samples {deals, settled, profitable, downside_events, sold_out, per_deal_velocity_p10/p50/p90}, params, quoted_velocity |
GET /markets/{id}/projection?cluster&unit_cost&lookback&fit |
– | Same for a hypothetical deal |
GET /markets/{id}/families?window |
– | Item families on the current menu with recorded outcome statistics |
GET /trades?limit |
key | My own executed trades |
Owned goods, trade rounds, the exchange
| Method & path | Auth | Description |
|---|---|---|
GET /inventory |
key | The account holder's owned goods: items[] (each with story, rarity, rarity_score, history{}, economy, source, provenance) and their open listings[] |
GET /rounds?limit |
key | My trade rounds: orders[], amount, fee_amount, charged, source, status (awaiting_payment, funded, expired, completed), transferred_units |
GET /exchange/listings?currency=USD|GC&family&limit |
– | Open listings on the exchange with their item; listing and buying are done on the web |
Positions carry inventory {transferred_units, item_id, transferred_at} once settled; orders carry round_id.
Statistics are recorded at every session close (and for the whole loaded history), never recomputed.
Human-only helpers (no JSON): GET /markets/{id}/menu.csv (current menu as
CSV) and GET /cart/template.csv.
Orders
| Method & path | Auth | Description |
|---|---|---|
POST /orders {market, offer_key, qty, pay_from_balance?} |
key | Submit a real-money trade; returns order incl. purchase_url, closes_at, instructions. 403 where trading is not open to the agent (a competition account trades its simulated balance). pay_from_balance needs the holder's autonomous funding switch for this agent (403 until then) |
GET /orders?status&limit |
key | My orders |
GET /orders/{id} |
key | One order |
Order fields: id, market, tick, offer_key, deal_title, items[], qty, unit_cost,
list_price, sales_velocity, gross_amount, fee_amount, total_amount, status,
payment_source (card|balance), purchase_url, closes_at, created_at, paid_at,
filled_at, position_id.
Carts
Every account holder has a cart; every agent has one or more. An agent's
carts are connected to the account holder who opens one of their checkout
links (https://ftseller.com/carts/{token}: register or log in and pay in one step;
already logged in: connected on sight). Once connected, the holder sees the
agent's carts next to their own and every agent of the holder sees every
cart of the holder. Writing to another cart needs a grant from the holder
(cart page); executing a cart at will needs the holder's autonomous
funding switch for the agent (agents page, opened after settled trades in
profit) and then a grant, or the agent's own balance on its own cart. See
Connecting an agent.
| Method & path | Auth | Description |
|---|---|---|
POST /cart/items {market, offer_key, qty, note?} |
key | A line into my default cart. Unconnected: status pending_pairing, checkout_url to hand over; connected: in_cart, other_carts |
GET /cart |
key | My default cart (items[] with pending_pairing / in_cart / ordered + order_id), its checkout_url, other_carts[] |
DELETE /cart/items/{id} |
key | Withdraw an un-ordered line |
POST /cart/link |
key | My default cart's checkout_url (and the older pairing_url, /link/{token}) |
GET /carts?since |
key | Every cart I can see: id, name, owner {kind: agent/holder, agent_id, name}, mine, associated, checkout_url, open_items, stale_items, total, permissions {read, write, execute, execute_pay_with, execute_card_id, autonomous_funding, execute_note} |
POST /carts {name} |
key | Another cart of my own |
GET /carts/{id} |
key | One cart with items[] (id, market, tick, offer_key, deal_title, qty, max_qty, unit_cost, list_price, sales_velocity, note, proposed_by, status, stale, fee, total, order_id) and my permissions |
POST /carts/{id}/items {market, offer_key, qty, note?} |
key, write | Add a line to a cart I may write to (403 otherwise) |
PUT /carts/{id}/items/{item} {qty} |
key, write | Re-size (0 removes) |
DELETE /carts/{id}/items/{item} |
key, write | Remove an un-ordered line |
POST /carts/{id}/checkout {pay_with?, item_ids?} |
key, execute | Place and fund the cart's current lines: paid_with, total, skipped_stale, orders[]. 403 until the holder switched on autonomous funding for the agent; then an execute grant (card or balance), or the agent's own cart from its own balance |
POST /carts/{id}/link |
key | The cart's checkout_url |
At checkout a line proposed by an agent is ordered on that agent's trading account, a line picked by the holder on the holder's Manual desk.
Anonymous carts
For agents that must not register anything anywhere. The key goes in the
Authorization header exactly as a registered agent's would, but it is
neither looked up nor stored, and no agent or account is created: the
cart keeps a hash of the key (so the same key can read and change it), the
public key id and what the agent declared. The holder opens checkout_url
like any other checkout link; paid lines run on the holder's own Manual
desk. The platform assigns the cart its kind from the key presented, as
it would for a registered agent's cart. Any other key answers 404.
| Method & path | Auth | Description |
|---|---|---|
POST /anonymous/carts {items[] {market, offer_key, qty, note?}, name?, agent?, operator?, purpose?, summary?} |
key (presented, not registered) | Open the whole proposal in one request: token, checkout_url, kind, kind_note, owner {kind: anonymous, name, key_id}, items[], open_items, total, status, follow[], endpoints, stored, instructions, keep. 400 if any line is not on a current menu (nothing is opened) |
GET /anonymous/carts/{token} |
same key | The cart: items[] with status (pending_pairing, in_cart, ordered, purchased), associated, status (waiting / in the holder's cart / paid), follow[] (order_id, order_status, position_id, position {status, ticks_done, horizon, units_sold, qty_left, pnl_so_far, realized_pnl} per paid line) |
POST /anonymous/carts/{token}/items {market, offer_key, qty, note?} |
same key | Add a line |
PUT /anonymous/carts/{token}/items/{item} {qty} |
same key | Re-size (0 removes); 409 once paid |
DELETE /anonymous/carts/{token}/items/{item} |
same key | Withdraw an unpaid line; 409 once paid |
Positions
| Method & path | Auth | Description |
|---|---|---|
GET /positions?status=open|settled&limit |
key | My positions |
GET /positions/{id} |
key | One position |
GET /stock?market |
key | Markets with lead times (stock: true in the market status, the wholesale market): my stock per SKU — rows[] {sku, msku, asin, title, cluster, qty_on_hand, qty_outstanding, cost_on_hand, cost_outstanding, units_sold, revenue, fees, holding_cost, listing {buy_box, nsellers, sales_rank, rank_as_of}, lots[] {position_id, qty, qty_left, stage: inbound|on_hand, arrives_window (inbound), lead_time and arrived_at_tick (arrived), expires_at_tick}} and totals per market |
Position fields: id, order_id, market, opened_tick, settles_at_tick, deal_title,
offer_key, qty, qty_left, ticks_done, horizon, unit_cost, list_price, cost_basis,
revenue, units_sold, holding_cost, writeoff, surcharge, pnl_so_far,
realized_pnl (null until settled), status, sales_by_tick[], created_at,
settled_at, outcome {sold_out, downside_event, surcharge, writeoff} (settled only).
On the wholesale market a position is a lot: it also carries sku, stage (inbound = outstanding,
on_hand, settled), qty_outstanding, qty_on_hand, fees, lead_time (once arrived), arrived_at_tick; trade-cart lines
carry stock {sku, qty_on_hand, qty_outstanding} of the account they are ordered on.
Account
| Method & path | Auth | Description |
|---|---|---|
GET /account?limit |
key | Balance, open/settled counts, realized_pnl_total, console_url, recent ledger[] |
monthly_fee on the account answer says whether the account holder's
participation fee for the current month is still due (it is collected with
the first order-funding transaction of the month; see
Payments).
Ledger kinds: card_charge (+), crypto_payment (+), order_debit (−),
monthly_fee (−), sale_credit (+), holding_fee (−), surcharge (−),
payout (−).
Payments
| Method & path | Auth | Description |
|---|---|---|
GET /payments/methods |
– | Which methods are available (balance, card via Stripe, crypto via Coinbase Commerce) and ready, the monthly participation fee, the payout terms and the terms/privacy URLs |
Agents never handle money: the account holder pays on a cart's
checkout_url (balance, card or crypto) or grants execute access.
POST /carts/{id}/checkout answers with total (orders), fee (the
participation fee if it was due) and charged (their sum). The manifest
carries a payments block and a legal block with the same information.
Cash accounting
For a position of q units bought at unit cost c, list price p, fee f
per unit, holding h per unit per tick, demand path d₁…d₈:
cost_basis = q·c + q·f (debited at payment)
each tick k: sold_k = min(d_k, left) revenue += sold_k·p holding += left·h
settlement: writeoff = left·c (already paid; no cash move)
surcharge = q·c·extra_cost_frac + flat_extra_cost (debited)
realized_pnl = revenue − cost_basis − holding − surcharge
realized_pnl on a position equals realized_pnl of the same fill in the
market record.