📦 FTSeller Wholesale Market Log in · Register

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/protocol as 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.