Skip to main content

Changelog

The API follows semantic versioning; the current version is 0.16.0. Breaking changes are called out explicitly, and the deploy pipeline runs a breaking-change diff on every spec change, so undeclared breaks fail CI before they can ship.

0.16.0 — 2026-09-25​

  • Added: GET /v1/credentials rows carry daily for API keys — today's accepted notional and order count against the key's daily cap — so an account can watch its agent keys' budgets without holding the keys.

0.15.0 — 2026-09-24​

  • Added: per-account funding payments (funding_payments_stream_spec). GET /v1/funding_payments — the caller's funding legs as the engine booked them (signed payment_uusdc, snapshot position_qty, mark_ticks, notional_uusdc, rate_fp8, dollar_capped), newest first, cursor-paginated, with page totals. GET /v1/funding_payments/summary — per-instrument net over 24 h / 7 d / all, the latest payment, the current position and mark, the last settled rate, and an explicitly-labelled predicted_next_uusdc estimate. History begins at the first boundary after this version is deployed.
  • Added: margin block positions carry funding_net_24h_uusdc and funding_last_uusdc (absent when unavailable) on GET /v1/snapshot and GET /v1/margin_info.
  • Added: WebSocket — private account channel funding_payment frame (one per holder leg, after the boundary's settlement_applied frames) and public market_lifecycle channel funding_rate frame (the market's boundary record).
  • Engine / audit: new egress record FundingPayment (template 242) per holder leg; the audit indexer stores it (sphinx.funding_payments) and mirrors each leg into ledger_entries as funding_debit / funding_credit.

0.14.0 — 2026-09-23​

  • Added: scoped agent credentials. POST /v1/credentials accepts instrument_allowlist, max_order_notional_uusdc, max_daily_notional_uusdc, max_open_orders, approval_mode (auto | require_human) and a free-form agent descriptor; Credential carries scope + agent (always present; unscoped = empty allowlist, zero caps, auto). The edge enforces the scope on POST /v1/orders before the sequencer — new 403 codes instrument_not_allowed, order_notional_cap, daily_notional_cap, open_orders_cap.
  • Added: human-in-the-loop orders. A require_human credential's POST /v1/orders returns 202 with a PendingOrder instead of submitting; GET /v1/orders/pending lists the account's parked orders; POST /v1/orders/pending/{id}/approve submits the stored order (attributed to the agent) and /reject records the decision — new codes approver_not_allowed (403), pending_already_decided, pending_expired (409). The private order WebSocket channel gains pending_approval, approval_approved, approval_rejected frames (pending_id, credential_id, agent, notional_uusdc, expires_at_ns, outcome).
  • Added: POST /v1/orders/stage returns scope (ScopeUsage: caps and today's usage) for scoped credentials and mirrors a cap the order would hit into would_reject with the warning credential_scope; approval_required warns that placing will park the order.
  • Added: GET /v1/account/limits returns scope, agent and daily (today's accepted notional and order count against the daily cap).
  • Added: fill attribution — GET /v1/fills rows carry credential_id and agent for the caller's leg when the order was placed by a 0.14.0+ edge.

0.13.0 — 2026-09-21​

  • Changed: GET /v1/instruments/{id}/candles now serves history beyond the edge's in-memory window from the venue's durable candle store, transparently (retention: 1m 120 days, 5m 2 years, 15m/1h/1d indefinitely; at most 12,000 bars per call — page back by moving to). Response shape unchanged.
  • Added: GET /v1/instruments/{id}/funding — public funding-rate history per perpetual, one record per boundary, newest first, cursor-paginated like the trade tape (rate_fp8 / premium_fp8 / interest_fp8 fixed-point 1e-8, skipped). New schemas FundingPage, FundingRate.

0.12.0 — 2026-09-20​

  • Added: POST /v1/orders/stage — server-side pre-trade preview from one engine snapshot, same body as POST /v1/orders, nothing published: the book (best bid/ask, mid, spread, mark), the execution walk (crossing vs resting count, VWAP, notional, slippage vs mid, max_cost binding), fee estimates (taker on the crossing part, maker on the rest, total cost, breakeven move), the netted initial-margin hold before/after per spec §5.4 (max-directional netting at the mark, reduce-only holds zero) with free collateral and utilization after, a best-effort would_reject mirror of the engine's admission checks, warnings, and a single-use confirm_token (60 s) bound to the order's canonical fields. New schema OrderPreview (+ PreviewBook, PreviewExecution, PreviewFees, PreviewMargin).
  • Added: optional confirm_token on PlaceOrderRequest; a presented token must redeem for this exact order or the placement fails 409 confirm_token_invalid (one code for unknown / expired / used / mismatched). Tokens are edge-memory: an edge restart voids them — re-stage.

0.11.0 — 2026-09-20​

Agent-access prerequisites (the venue table stakes an autonomous client needs before any agentic surface): fee visibility, self-expiring keys, a lifecycle stream, a kill primitive, and budgets as data. No engine changes.

  • Added: fee_uusdc (signed µUSDC, own leg) and tag (normal|liquidation|adl) on GET /v1/fills rows — parity with the WS fill frame. Rows indexed before the fee columns existed read 0/normal.
  • Added: GET /v1/fees — public, keyless fee schedule per margined listing (signed maker/taker bps, flat fees, funding/delivery flags). New schema FeeSchedule.
  • Added: expires_in_s on CreateCredentialRequest (0 = never, max 366 days); expired keys fail 401 like expired sessions. POST /v1/credentials gains 400 invalid_request.
  • Added: private WS channel order — the account's order lifecycle (accepted_fully_filled / accepted_rested / accepted_ioc_canceled / rejected / canceled / cancel_rejected) off the engine egress. AsyncAPI message order, schema OrderData.
  • Changed: POST /v1/orders/cancel_all — instrument_id is now optional; omitted/0 runs an account-scope sweep (one MassCancel per listed instrument). Response gains scope and, for account scope, mass_cancels (per-instrument correlation ids). The former 400 for a missing instrument_id is gone; 429 and 503 added.
  • Added: POST /v1/deadman / GET /v1/deadman — cancel-on-silence switch (arm/refresh/disarm a 1–300 s TTL; fires an account-scope cancel, one-shot; edge-evaluated, so an edge restart disarms). Schemas DeadmanRequest, DeadmanState.
  • Added: GET /v1/account/limits — the calling credential's write budget (limit / window / remaining / reset) as data, mirrored on write responses as X-RateLimit-Limit|Remaining|Reset (+ Retry-After on 429). The limiter ships disabled on staging (enabled: false); the write endpoints document 429 rate_limited for when it is on. Schema AccountLimits.

0.10.0 — 2026-09-17​

  • Added: GET /v1/power/ercot/prices — public reference prices for ERCOT's 15 hub and load-zone settlement points: the latest 5-minute SCED LMP and the latest 15-minute real-time settlement point price (the RT perps' settlement basis) per point, each with ERCOT's own timestamp and a stale flag, plus the RT perpetual listed on the point. µUSDC per MWh; prices may be negative and are null until first observed. Cached 10 s, rate-limited. Powers the terminal's congestion map. New Power tag; new schemas PowerPrices, PowerPoint, PowerPrice. Reference data only — not an index or settlement input.

0.9.0 — 2026-09-06​

  • Added: e-mail-proven auth flows on the password path — POST /v1/auth/confirm (consume a confirmation link), POST /v1/auth/confirm/resend, POST /v1/auth/password/forgot (always 202, link sent asynchronously), POST /v1/auth/password/reset (new password, every session revoked, fresh session), and POST /v1/auth/password/change (current password required, other sessions revoked). Tokens are single-use, hashed at rest, 24 h (confirm) / 30 min (reset). New error token_invalid (one code for every token failure).
  • Added: email_verified on SessionResponse (required) and on GET /v1/accounts/me for password accounts; confirmation on the signup response (sent / failed).
  • Breaking: on venues that arm the verified-e-mail gate (staging does), a password-signup account that has not used its confirmation link receives 403 email_unverified on every mutating call — orders, cancels, margin transfers, withdrawals, POST /v1/credentials. Reads are unaffected; API-key accounts are never gated; accounts that existed before 0.9.0 were marked verified at migration.
  • Changed: POST /admin/operators requires the target login to be e-mail verified (409 email_unverified). New internal verb POST /admin/accounts/{account_id}/email/verify (operator force-verify).

0.8.0 — 2026-09-02​

  • Added: GET /v1/status — public per-service health for the terminal's Status view: each venue service reported ok/down with probe latency, and an overall ok/degraded/down verdict (core services down ⇒ down). Cached 5 s, rate-limited.
  • Added: POST /v1/support/tickets — the terminal's Support form. multipart/form-data with subject, message, and up to four image attachments (content-sniffed PNG/JPEG/GIF/WebP; ≤4 MB each, ≤10 MB total). The ticket is stored before any delivery attempt; delivery reports sent (support inbox e-mailed) or queued (no mail relay configured, or delivery failed — the ticket is kept). New Support tag.

0.7.0 — 2026-09-02​

  • Added: contract_size + unit on the contract manifest (GET /v1/contract_specs, ContractSpec) and the metadata registry (GET /v1/instrument/margin_metadata, MarginMetadata): the physical contract definition — unit is what one contract is denominated in (MWh, BTC), contract_size how many units one contract delivers (1 for every current listing; contract-notional pricing makes a contract's notional at price P exactly P). Recorded at listing time, never on the engine wire. POST /admin/margin/instruments now requires unit (empty → 400); contract_size defaults to 1. Both are console-amendable. Existing listings were backfilled (BTC/ETH by base, ERCOT hubs + zones → MWh).
  • Note: the manifest is content-addressed, so every manifest_hash changed with this release — clients that pinned a hash re-fetch the manifest once; the hashing rule (SHA-256 of the canonical spec bytes) is unchanged.

0.6.0 — 2026-09-02​

  • Added: order reject reason instrument_not_found (wire code 53, HTTP 404) — an order naming a nonexistent instrument now gets its own reject. Until now the engine aliased this case onto instrument_halted (wire 32, HTTP 409), which read as a phantom halt during incident forensics; the split shipped venue-side 2026-09-02. Existing instrument_halted semantics are unchanged.
  • Docs: all internal planning tags stripped from customer-visible spec text — (V0.1) summary prefixes, (API-n)/(F-6) ticket suffixes, LQ-16/POWER v12/phase labels, and market_maker/PLAN.md reference pointers. Meaning-bearing parentheticals (dev only, reporter only, paginated) kept.
  • Process: this release arms the version gate (see policy above) and normalizes historical changelog headings to one format; the out-of-band [1.0.0] margin-era heading from 2026-07 is retitled below (it recorded a contract milestone, not a released 1.0 — the version line has been 0.x throughout).

0.5.0 — 2026-08-31​

  • Added: GET /v1/open_interest — public per-instrument open interest for margined listings (contracts + mark-notional µUSDC) at one engine_seq_applied, served from the edge's single-flight ~2s engine- snapshot cache. Closes the terminal's hardcoded-zero OI column; the operator ratified publishing near-real-time OI (2026-08-31).

0.4.0 — 2026-08-30​

Docs-site groundwork (api_docs_site_spec.md Phase B): the spec closes its two external-surface gaps and gains a public variant for the documentation site at docs.staging.sphx.io.

  • Added: POST /v1/margin/transfer — user-initiated margin sub-account cash transfer (direction in/out, µUSDC, optional client transfer_id); 202 accepted-for-sequencing semantics. The endpoint shipped with the B2 margin slice; the spec had never documented it.
  • Added: GET /healthz/venue — the public venue-functional liveness probe (two-sided books, fill-tape freshness, disk headroom). HTTP 200 for both verdicts; monitors match on data.status.
  • Added: x-internal: true on all 8 operator (/admin/*) operations, and a generated openapi.external.yaml (gen_openapi.py --external logic, emitted alongside the full spec): drops x-internal operations, prunes unreferenced schemas, unused security schemes, and empty tags. The docs site renders the external variant; the full spec remains the internal contract of record. --check now covers both files.
  • Changed: servers: — staging marked LIVE (it has been since 2026-08-01); production stays a placeholder.
  • Added: asyncapi.yaml — an AsyncAPI 3.0 document for the WebSocket surface (EDGE-085): the session envelope, all 7 external channels, and every event's exact payload, verified field-by-field against the edge's emitting code. The docs site renders it as the streaming reference and serves it at /asyncapi.yaml. Parity fixes folded back into websockets.md: the mass-cancel field is sequencer_correlation_id (the TS union said correlation_id), and the fill arm gains fee_uusdc + tag.
  • Added: operationId + tags on the five margin-era read endpoints (margin_info, insurance_fund, liquidation_metrics, contract_specs ×2) — they rendered as UNTAGGED in generated docs.
  • Fixed: the /v1/ws stub's channel list now names all seven public/ private channels (trade, mark, account were missing); private channels corrected to fill + account. New Margin tag groups the margin-domain operations.

0.3.1 — 2026-08-09​

Email+password auth for outside users (auth review option A, edge d4dd5cc). Passwords are a credential factory, not a parallel auth system: signup/login mint a short-lived session credential (label session, 7-day expiry) and return the same <prefix>.<suffix> bearer API keys use, so every authenticated endpoint accepts both token kinds unchanged; logout is credential revocation.

  • Added: POST /v1/auth/signup — anonymous. Creates the account (entitlement mask 7 — no APITrading for outside users), stores the password (argon2id), returns SessionResponse (account, one-time session_token, expires_at_ns, and — on faucet-enabled dev/staging venues — faucet_uusdc, sequenced asynchronously). 409 email_taken, 429 rate_limited (10 / 5 min per IP).
  • Added: POST /v1/auth/login — anonymous. Uniform 401 for unknown-email vs wrong-password (equalized timing); 429 rate_limited (10 / 5 min per IP+email).
  • Added: POST /v1/auth/logout — Bearer. Revokes the CURRENT session credential; an API key calling it gets 400 invalid_request (revoke keys via DELETE /v1/credentials/{credential_id}).
  • Added: shared Conflict (409) and RateLimited (429) response components; Auth tag.

0.3.0 — 2026-08-05​

REMOVED (breaking): the funding-rate history surface — the GET /v1/instruments/{instrument_id}/funding REST endpoint and the public funding WebSocket channel. The terminal's funding-line overlay it fed was retired (operator decision 2026-08-05); realized funding remains fully visible through the account channel's settlement events and the snapshot's reserved_funding. The engine's funding MECHANISM is unchanged.

0.2.5 — 2026-08-04​

  • POWER v12 (power_products_spec §4.2): ContractSpec gains has_price_domain / min_price_ticks / max_price_ticks / exposure_floor_uusdc / funding_dollar_cap_daily_rate_bps / funding_dollar_cap_lookback_hours; the margined listing request accepts the same fields. GET /v1/instruments/{id}/orderbook adds yes_asks (side-native margined SELLS — signed ticks; the §11.9 no_bids complement remains event-class only). side=no candles are rejected on margined instruments. Zero-behavior-change for existing listings: domain fields are flag-gated, floors default 0.

0.2.4 — 2026-08-01 — candle backfill (dev/staging, additive)​

  • Added: POST /admin/candles/backfill — seeds historical bars into the in-memory candle aggregator (non-sequenced projection; refuses bars at/after the first live fill). Companion tool tools/candle-backfill sources real market history per instrument (feed-grammar-driven source registry) and strike-derives binary probability paths from the underlying.

0.2.3 — 2026-07-20 — funding surface completed + margined metadata registry (additive)​

Driven by the live-frontend integration (createEdgeProvider): the terminal's class mappers need a public identity record for margined instruments, and the funding surface the docs promised is now real.

  • Added: GET /v1/instruments/{instrument_id}/funding — public, paginated funding-rate history (trades-style cursor), projected from the Audit Indexer's funding_rates table. Closes the docs' GET /v1/funding_history promise (that flat route was never implemented; the nested house pattern won).
  • Added: funding WS channel — public, per-instrument, one frame per funding boundary (egress 241), previously documented but not relayed by Edge. skipped uses the indexer's rule so stream and backfill agree.
  • Added: GET /v1/instrument/margin_metadata — the margined-instrument metadata registry (identity/display: unique symbol, display_name, base/quote/expiry_date derived from the listing's price_feed_id). Recorded by the instrument service at listing time; completes the two-views model for margined instruments.
  • Added: optional symbol / display_name on POST /admin/margin/instruments (metadata-registry overrides; derived when omitted; symbol collisions rejected 400 pre-sequencing).
  • Fixed: reduce_only / post_only — documented on PlaceOrderRequest since the derivatives release but never accepted by the Edge decoder (unknown field); now plumbed through to the engine's RC-3 flags byte (idempotency body-hash covers them). Caught by the first spec-conformant client (the live frontend provider).
  • Fixed: Edge's reject-reason strings stopped at code 33 — every margin-era reject (34–52: no_mark_yet, outside_price_band, margined_requires_margin_sub, …) surfaced as "unknown" despite being documented in the RejectReason code table. Map completed; the OrderRejectReason string enum now lists all 52.
  • Fixed: GET /v1/instruments/{id}/candles emitted "bars": null (Go nil slice) instead of the documented empty array.
  • Fixed: both /v1/contract_specs* responses were documented WITHOUT the standard envelope; the server has always wrapped them.
  • Added: POST /admin/margin/instruments now accepts (and for futures REQUIRES) daily_settlement_time_utc_seconds + expiry_datetime_utc_ns; the funding block is venue-default-derived for perps (§10.2 leaves no freedom). Without these the admin path could never list a valid perp or dated future.
  • Fixed: admin.md still documented kebab-case /admin/custody/simulate-deposit after the F4 rename (code serves simulate_deposit); MarginListingResult.required still named the pre-F6 correlation_id.

2026-07-19 — API surface audit, HIGH+MEDIUM findings applied (pre-policy: shipped unversioned)​

  • Added: CORS support (F1) — EDGE_CORS_ORIGINS allowlist; empty = permissive dev default. Preflights answered with explicit Authorization/X-Admin-Key/X-Reporter-Key.
  • Added: order idempotency (F2) — optional client_order_id (≤64 chars) on POST /v1/orders; same-key retry replays the original ack within a ~15-min Edge window; conflicts → 409 client_order_id_reuse / order_outcome_unknown.
  • Added: GET /v1/fills pagination (F3) — cursor param; response is now {fills, next_cursor} (BREAKING shape change from a bare array).
  • Renamed (F4, snake_case house rule): POST /admin/seed-balance → /admin/seed_balance; POST /admin/custody/simulate-deposit → /admin/custody/simulate_deposit.
  • Renamed (F5): GET /v1/contract_spec/{instrument_id} → GET /v1/contract_specs/{instrument_id} (plural-collection rule).
  • Renamed (F6): every PAYLOAD-level integer correlation_id (order/cancel/ withdrawal/admin acks, order detail, fills, margin listing result, WS cancel_all completion) → sequencer_correlation_id. The ENVELOPE's string correlation_id (HTTP trace) is unchanged. Full audit: docs/exchange/api_surface_audit.md.

2026-07-18 — price_feed_id on margined listings (pre-policy: shipped unversioned)​

  • POST /admin/margin/instruments: new REQUIRED price_feed_id — the data-oracle index feed pricing the instrument (the manifest's authoritative feed↔contract record; format-checked against the feed grammar + index-qualifier rule; never forwarded to the 341 wire).

2026-07-17 — price/resolution split (pre-policy: shipped unversioned)​

  • Moved: POST /v1/instrument/submit → POST /v1/instrument/resolution/submit (behavior unchanged; the handler moved to the resolution component, sphinx.resolution.v1.ResolutionService).
  • Added: GET /v1/instrument/resolution/preview/{instrument_id} (reporter-gated) — the advisory b′ operator-confirm aid: last journaled selected index value + staleness.
  • Backend (not wire-visible here): index prices now enter via the price component's data-oracle ingest (sphinx.price.v1.PriceIngestService, gRPC :18205); IndexPriceSet and ResolutionFinal are signed with separate per-component keys. Decision record: docs/exchange/price_resolution_split.md.

2026-07 — Margin-era (derivatives) surface (contract milestone; recorded out-of-band as "1.0.0" at the time — no 1.0 was released)​

The margined-derivatives layer is IMPLEMENTED and ships alongside the fully-collateralized event-contract product. The margin-era surface lands as one SemVer-coherent contract change (DRV-101, API-1..API-5); partial adoption is prohibited. The full contract is in margin_era_api.md and the exchange repo's margin-era openapi.yaml (pending merge into this directory's openapi.yaml). Behavior is normative in the derivatives spec (../exchange_derivatives_engine_spec.md); this entry records only the API surface delta.

Added — REST​

  • GET /v1/snapshot margin block — per-sub-account equity, im_held, mm_required, free_collateral, margin_withdrawable, reserved_funding, at the same engine_seq_applied as balances/positions (spec §14).
  • GET /v1/margin_info — same-seq read-only projection of the margin block alone.
  • GET /v1/contract_spec/{instrument_id} + GET /v1/contract_specs — per-instrument margined manifest (pinned by manifest_hash) + listing (spec §14.2).
  • GET /v1/insurance_fund — balance + itemized flow journal (spec §7).
  • GET /v1/liquidation_metrics — per-instrument hourly liquidation aggregates, T+24h delayed.
  • POST /v1/margin/transfer — free-USDC transfer across the margin sub-account boundary (the sequenced MarginTransfer event; spec §14, §11).

Added — WebSocket​

  • public mark — engine-derived mark (from the signed hourly IndexPriceSet, spec §4) + RC-1 admission band, per instrument.
  • public funding — perpetual funding rate + index (perps only).
  • account channel (private) gains margin_warning, liquidation_started, liquidation_complete (spec §14.2).

Added — order surface​

  • reduce_only / post_only order flags on POST /v1/orders (spec §8.4).
  • Margin-era reject codes (wire reason byte 30–49; spec §8) — see orders.md.

Changed​

  • Position shape: margined positions serve signed_qty + last_settlement_price; event contracts dual-serve yes_position / no_position alongside the YES-collapsed signed_qty for at least one client-release cycle (spec §14.5).

0.2.2 — 2026-05-28 — Edge publish-path concurrency hotfix​

Fixed​

  • Concurrent POST /v1/orders (and cancel / mass-cancel / withdrawal / admin publishes) could corrupt each other's wire bytes under load, surfacing as a spurious 400 invalid_api_fields on a valid order plus an orphaned-but-rested order the client never tracked (market-maker MM-3 finding — see ../exchange/bug_reports/02_engine-rest-cluster-divergent-place.md).

    Root cause was in Edge, not the engine: the shared aeron SequencerClient reuses a single SBE encode buffer and is documented not-thread-safe, but Edge drove it from many concurrent HTTP handler goroutines through one Forwarder with no publish serialization. Concurrent publishes interleaved their writes into that buffer; the engine then decoded torn frames as garbage fields (→ invalid_api_fields) or mis-routed a torn correlation_id.

    Fix: a Forwarder.publishMu mutex held around every publish into the client (encode + Offer is sub-µs). Added a -race regression test (services/edge/internal/forwarder/forwarder_race_test.go) + a make test-race target. No public API change; the JSON request/response shapes of POST /v1/orders are unchanged. info.version bumped to 0.2.2 per SemVer patch. A deeper follow-up (per-call encode buffer in the shared client) is tracked for V1.

0.2.1 — 2026-05-28 — Snapshot consistency hotfix​

Fixed​

  • Engine SnapshotState gRPC race that produced internally-inconsistent snapshots under fill load. The handler read last_seq_applied first, then iterated CollateralTable / PositionTable / open orders / risk tables without synchronization against the apply thread. The apply thread could commit one or more events between the seq capture and the table iteration, so the snapshot reported seq_applied = N while the iterated tables reflected state at seq_applied ≥ N+1. Replay Verifier flagged collateral mismatch divergences on every soak that produced fills (market-maker finding — see ../exchange/bug_reports/01_engine-snapshot-collateral-race.md).

    Fix has two layers: per-table std::mutex on CollateralTable and PositionTable (closes the data-race UB on the underlying std::unordered_map), plus a new ApplyLoop::snapshot_read_lock() — a std::shared_mutex the apply thread holds UNIQUE around the full on_event body, and SnapshotState holds SHARED for the entire handler. Together they give snapshot callers a coherent (seq_applied, balances, positions, collateral, open orders, account/instrument states) tuple at one seq boundary. Adds the concurrent regression test services/trading_engine/test/test_snapshot_consistency.cpp.

    No public API change — pure backend correctness; the JSON shape of GET /v1/snapshot is unchanged. info.version bumped to 0.2.1 per SemVer patch convention.

0.2.0 — 2026-05-28 — V0.1 MM-readiness API additions​

Shipped as repo tag v0.2.0 (parent + docs). Additive overlay on the V0 baseline, scoped to support the market-maker test harness described in ../../market_maker/PLAN.md. The negotiated cut and ordering are recorded in market_maker/PLAN.md §16.

Added — REST​

  • GET /v1/orders/{order_id} — look up an order's full lifecycle state, including terminal (filled/canceled/rejected). Closes the "order disappeared from /v1/orders — what happened?" reconciliation hole.
  • POST /v1/orders/cancel_all — per-instrument mass cancel (instrument_id required; no all-instruments form, no kill-switch). Backed by a MassCancel SBE event; asynchronous — returns 202 Accepted with a correlation_id, then the engine emits one OrderCanceled per affected order (audited + orderbook_delta) and a terminating MassCancelComplete surfaced as a mass_cancel_complete event on the account WS channel.
  • GET /v1/snapshot — atomic per-account snapshot: balances + positions + open orders + pending withdrawals, all at the same engine_seq_applied. Replaces multi-poll reconciler pattern.
  • GET /v1/instruments/{instrument_id}/trades — paginated, anonymised public trade tape.

Added — WebSocket​

  • account channel (private) — non-fill account events emitted in V0.1: deposit_credited, withdrawal_state, account_state_change, mass_cancel_complete. (An entitlement_change event shape is specified for V1 forward-compat but is not emitted in V0.1 — see ws.md §account channel.)
  • trade channel (public) — anonymised public tape, one event per match.

Changed​

  • orderbook_delta WS push extended from signal-only to a payload carrying the changed levels (changes: [{side, price_pips, new_count}]; side ∈ yes_bid/no_bid, new_count = 0 removes a level). Subscribers apply diffs in place instead of re-snapshotting on every notification. The push is now emitted only when the book actually changes — a rejected / no-op order no longer produces a (previously empty) signal.
  • POST /v1/orders responses now include three timing-breadcrumb fields: edge_received_at_ns, sequencer_committed_at_ns, engine_applied_at_ns (Unix ns; sequencer_committed_at_ns carries millisecond precision — Aeron cluster time ×1e6 — see orders.md). Lets clients decompose round-trip latency by stage.

Deferred to V1 (recorded here so consumers don't anticipate them in V0.1)​

  • PATCH /v1/orders/{order_id} (atomic modify) — locked design in market_maker/PLAN.md §16.3. Workaround for V0.1 clients is cancel-and-replace.
  • GET /v1/fees, GET /v1/risk_limits — V1 polish.
  • Idempotency-Key header on POST /v1/orders — V1 (existing backlog in orders.md §"Treating engine_timeout correctly").

0.1.0 — 2026-05-18 — V0 baseline​

Initial documented spec, captured after the V0 SHIPPED milestone. The surface as documented here is the V0 acceptance gate state, validated by the full V0 user-journey integration test and the 30-iteration soak.

Added (relative to "no spec")​

  • Accounts: POST /v1/accounts, GET /v1/accounts/me.
  • Credentials: POST /v1/credentials, GET /v1/credentials, DELETE /v1/credentials/{credential_id}.
  • Orders: POST /v1/orders, DELETE /v1/orders/{order_id}, GET /v1/orders.
  • Market data: GET /v1/instruments, GET /v1/instruments/{instrument_id}, GET /v1/instruments/{instrument_id}/orderbook, GET /v1/balances, GET /v1/positions, GET /v1/fills.
  • Candles: GET /v1/instruments/{instrument_id}/candles with resolution ∈ 1d.
  • Custody (customer): GET /v1/deposits/addresses, POST /v1/withdrawals, GET /v1/withdrawals/{withdrawal_id}.
  • Instrument service (read): GET /v1/instrument/instruments, GET /v1/instrument/instruments/{instrument_id}.
  • Instrument service (reporter): POST /v1/instrument/submit (gated by X-Reporter-Key).
  • WebSocket: GET /v1/ws with channels orderbook_delta, fill, market_lifecycle, candles.
  • Admin: POST /admin/seed-balance, POST /admin/accounts/{id}/state, POST /admin/instruments/{id}/state, POST /admin/instrument/instruments, POST /admin/instrument/instruments/{id}/state, POST /admin/custody/simulate-deposit.
  • Operations: GET /healthz, GET /readyz, GET /metrics (on the health port 8080).

Conventions​

  • Integer-pip pricing (1–9999 = 0.0001–0.9999).
  • USDC atomic units (6 decimals).
  • Timestamps in Unix nanoseconds (*_at_ns) or seconds (*_unix_seconds).
  • Response envelope { data, correlation_id } for success, { error: { code, message, details }, correlation_id } for failure.
  • Three auth schemes: Bearer (customers), X-Admin-Key (operators), X-Reporter-Key (reporters).

Known historical artefacts (do not "fix" without a coordinated client migration)​

  • Fill.ts_ns is sourced from Aeron Cluster's onSessionMessage timestamp, which is Unix milliseconds, not nanoseconds. The schema name is retained for backward compatibility with the cluster wire format; see market-data.md.