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/credentialsrows carrydailyfor 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 (signedpayment_uusdc, snapshotposition_qty,mark_ticks,notional_uusdc,rate_fp8,dollar_capped), newest first, cursor-paginated, with pagetotals.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-labelledpredicted_next_uusdcestimate. History begins at the first boundary after this version is deployed. - Added: margin block positions carry
funding_net_24h_uusdcandfunding_last_uusdc(absent when unavailable) onGET /v1/snapshotandGET /v1/margin_info. - Added: WebSocket — private
accountchannelfunding_paymentframe (one per holder leg, after the boundary'ssettlement_appliedframes) and publicmarket_lifecyclechannelfunding_rateframe (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 intoledger_entriesasfunding_debit/funding_credit.
0.14.0 — 2026-09-23
- Added: scoped agent credentials.
POST /v1/credentialsacceptsinstrument_allowlist,max_order_notional_uusdc,max_daily_notional_uusdc,max_open_orders,approval_mode(auto|require_human) and a free-formagentdescriptor;Credentialcarriesscope+agent(always present; unscoped = empty allowlist, zero caps,auto). The edge enforces the scope onPOST /v1/ordersbefore the sequencer — new403codesinstrument_not_allowed,order_notional_cap,daily_notional_cap,open_orders_cap. - Added: human-in-the-loop orders. A
require_humancredential'sPOST /v1/ordersreturns202with aPendingOrderinstead of submitting;GET /v1/orders/pendinglists the account's parked orders;POST /v1/orders/pending/{id}/approvesubmits the stored order (attributed to the agent) and/rejectrecords the decision — new codesapprover_not_allowed(403),pending_already_decided,pending_expired(409). The privateorderWebSocket channel gainspending_approval,approval_approved,approval_rejectedframes (pending_id,credential_id,agent,notional_uusdc,expires_at_ns,outcome). - Added:
POST /v1/orders/stagereturnsscope(ScopeUsage: caps and today's usage) for scoped credentials and mirrors a cap the order would hit intowould_rejectwith the warningcredential_scope;approval_requiredwarns that placing will park the order. - Added:
GET /v1/account/limitsreturnsscope,agentanddaily(today's accepted notional and order count against the daily cap). - Added: fill attribution —
GET /v1/fillsrows carrycredential_idandagentfor 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}/candlesnow serves history beyond the edge's in-memory window from the venue's durable candle store, transparently (retention:1m120 days,5m2 years,15m/1h/1dindefinitely; at most 12,000 bars per call — page back by movingto). 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_fp8fixed-point 1e-8,skipped). New schemasFundingPage,FundingRate.
0.12.0 — 2026-09-20
- Added:
POST /v1/orders/stage— server-side pre-trade preview from one engine snapshot, same body asPOST /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_costbinding), 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-effortwould_rejectmirror of the engine's admission checks,warnings, and a single-useconfirm_token(60 s) bound to the order's canonical fields. New schemaOrderPreview(+PreviewBook,PreviewExecution,PreviewFees,PreviewMargin). - Added: optional
confirm_tokenonPlaceOrderRequest; a presented token must redeem for this exact order or the placement fails409 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) andtag(normal|liquidation|adl) onGET /v1/fillsrows — parity with the WSfillframe. Rows indexed before the fee columns existed read0/normal. - Added:
GET /v1/fees— public, keyless fee schedule per margined listing (signed maker/taker bps, flat fees, funding/delivery flags). New schemaFeeSchedule. - Added:
expires_in_sonCreateCredentialRequest(0 = never, max 366 days); expired keys fail401like expired sessions.POST /v1/credentialsgains400 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 messageorder, schemaOrderData. - Changed:
POST /v1/orders/cancel_all—instrument_idis now optional; omitted/0runs an account-scope sweep (oneMassCancelper listed instrument). Response gainsscopeand, for account scope,mass_cancels(per-instrument correlation ids). The former400for a missinginstrument_idis gone;429and503added. - 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). SchemasDeadmanRequest,DeadmanState. - Added:
GET /v1/account/limits— the calling credential's write budget (limit / window / remaining / reset) as data, mirrored on write responses asX-RateLimit-Limit|Remaining|Reset(+Retry-Afteron429). The limiter ships disabled on staging (enabled: false); the write endpoints document429 rate_limitedfor when it is on. SchemaAccountLimits.
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 astaleflag, plus the RT perpetual listed on the point. µUSDC per MWh; prices may be negative and arenulluntil first observed. Cached 10 s, rate-limited. Powers the terminal's congestion map. NewPowertag; new schemasPowerPrices,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(always202, link sent asynchronously),POST /v1/auth/password/reset(new password, every session revoked, fresh session), andPOST /v1/auth/password/change(current password required, other sessions revoked). Tokens are single-use, hashed at rest, 24 h (confirm) / 30 min (reset). New errortoken_invalid(one code for every token failure). - Added:
email_verifiedonSessionResponse(required) and onGET /v1/accounts/mefor password accounts;confirmationon 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_unverifiedon 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/operatorsrequires the target login to be e-mail verified (409 email_unverified). New internal verbPOST /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 reportedok/downwith probe latency, and an overallok/degraded/downverdict (core services down ⇒down). Cached 5 s, rate-limited. - Added:
POST /v1/support/tickets— the terminal's Support form.multipart/form-datawithsubject,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;deliveryreportssent(support inbox e-mailed) orqueued(no mail relay configured, or delivery failed — the ticket is kept). NewSupporttag.
0.7.0 — 2026-09-02
- Added:
contract_size+uniton the contract manifest (GET /v1/contract_specs,ContractSpec) and the metadata registry (GET /v1/instrument/margin_metadata,MarginMetadata): the physical contract definition —unitis what one contract is denominated in (MWh,BTC),contract_sizehow 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/instrumentsnow requiresunit(empty → 400);contract_sizedefaults 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_hashchanged with this release — clients that pinned a hash re-fetch the manifest once; the hashing rule (SHA-256 of the canonicalspecbytes) 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 ontoinstrument_halted(wire 32, HTTP 409), which read as a phantom halt during incident forensics; the split shipped venue-side 2026-09-02. Existinginstrument_haltedsemantics 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, andmarket_maker/PLAN.mdreference 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 oneengine_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 (directionin/out, µUSDC, optional clienttransfer_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 ondata.status. - Added:
x-internal: trueon all 8 operator (/admin/*) operations, and a generatedopenapi.external.yaml(gen_openapi.py--externallogic, 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.--checknow 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 intowebsockets.md: the mass-cancel field issequencer_correlation_id(the TS union saidcorrelation_id), and the fill arm gainsfee_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/wsstub's channel list now names all seven public/ private channels (trade,mark,accountwere missing); private channels corrected tofill+account. NewMargintag 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), returnsSessionResponse(account, one-timesession_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. Uniform401for 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 gets400 invalid_request(revoke keys viaDELETE /v1/credentials/{credential_id}). - Added: shared
Conflict(409) andRateLimited(429) response components;Authtag.
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):
ContractSpecgainshas_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}/orderbookaddsyes_asks(side-native margined SELLS — signed ticks; the §11.9 no_bids complement remains event-class only).side=nocandles 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 tooltools/candle-backfillsources 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'sfunding_ratestable. Closes the docs'GET /v1/funding_historypromise (that flat route was never implemented; the nested house pattern won). - Added:
fundingWS channel — public, per-instrument, one frame per funding boundary (egress 241), previously documented but not relayed by Edge.skippeduses the indexer's rule so stream and backfill agree. - Added:
GET /v1/instrument/margin_metadata— the margined-instrument metadata registry (identity/display: uniquesymbol,display_name,base/quote/expiry_datederived from the listing'sprice_feed_id). Recorded by the instrument service at listing time; completes the two-views model for margined instruments. - Added: optional
symbol/display_nameonPOST /admin/margin/instruments(metadata-registry overrides; derived when omitted; symbol collisions rejected 400 pre-sequencing). - Fixed:
reduce_only/post_only— documented onPlaceOrderRequestsince 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 theRejectReasoncode table. Map completed; theOrderRejectReasonstring enum now lists all 52. - Fixed:
GET /v1/instruments/{id}/candlesemitted"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/instrumentsnow 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.mdstill documented kebab-case/admin/custody/simulate-depositafter the F4 rename (code servessimulate_deposit);MarginListingResult.requiredstill named the pre-F6correlation_id.
2026-07-19 — API surface audit, HIGH+MEDIUM findings applied (pre-policy: shipped unversioned)
- Added: CORS support (F1) —
EDGE_CORS_ORIGINSallowlist; 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) onPOST /v1/orders; same-key retry replays the original ack within a ~15-min Edge window; conflicts → 409client_order_id_reuse/order_outcome_unknown. - Added:
GET /v1/fillspagination (F3) —cursorparam; 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 stringcorrelation_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 REQUIREDprice_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/snapshotmargin block — per-sub-accountequity,im_held,mm_required,free_collateral,margin_withdrawable,reserved_funding, at the sameengine_seq_appliedas 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 bymanifest_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 sequencedMarginTransferevent; spec §14, §11).
Added — WebSocket
- public
mark— engine-derived mark (from the signed hourlyIndexPriceSet, spec §4) + RC-1 admission band, per instrument. - public
funding— perpetual funding rate + index (perps only). accountchannel (private) gainsmargin_warning,liquidation_started,liquidation_complete(spec §14.2).
Added — order surface
reduce_only/post_onlyorder flags onPOST /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-serveyes_position/no_positionalongside the YES-collapsedsigned_qtyfor 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 spurious400 invalid_api_fieldson 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
SequencerClientreuses a single SBE encode buffer and is documented not-thread-safe, but Edge drove it from many concurrent HTTP handler goroutines through oneForwarderwith 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 torncorrelation_id.Fix: a
Forwarder.publishMumutex held around every publish into the client (encode + Offer is sub-µs). Added a-raceregression test (services/edge/internal/forwarder/forwarder_race_test.go) + amake test-racetarget. No public API change; the JSON request/response shapes ofPOST /v1/ordersare unchanged.info.versionbumped to0.2.2per 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
SnapshotStategRPC race that produced internally-inconsistent snapshots under fill load. The handler readlast_seq_appliedfirst, then iteratedCollateralTable/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 reportedseq_applied = Nwhile the iterated tables reflected state atseq_applied ≥ N+1. Replay Verifier flaggedcollateral mismatchdivergences 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::mutexonCollateralTableandPositionTable(closes the data-race UB on the underlyingstd::unordered_map), plus a newApplyLoop::snapshot_read_lock()— astd::shared_mutexthe apply thread holds UNIQUE around the fullon_eventbody, andSnapshotStateholds 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 testservices/trading_engine/test/test_snapshot_consistency.cpp.No public API change — pure backend correctness; the JSON shape of
GET /v1/snapshotis unchanged.info.versionbumped to0.2.1per 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_idrequired; no all-instruments form, no kill-switch). Backed by aMassCancelSBE event; asynchronous — returns202 Acceptedwith acorrelation_id, then the engine emits oneOrderCanceledper affected order (audited +orderbook_delta) and a terminatingMassCancelCompletesurfaced as amass_cancel_completeevent on theaccountWS channel.GET /v1/snapshot— atomic per-account snapshot: balances + positions + open orders + pending withdrawals, all at the sameengine_seq_applied. Replaces multi-poll reconciler pattern.GET /v1/instruments/{instrument_id}/trades— paginated, anonymised public trade tape.
Added — WebSocket
accountchannel (private) — non-fill account events emitted in V0.1:deposit_credited,withdrawal_state,account_state_change,mass_cancel_complete. (Anentitlement_changeevent shape is specified for V1 forward-compat but is not emitted in V0.1 — seews.md§accountchannel.)tradechannel (public) — anonymised public tape, one event per match.
Changed
orderbook_deltaWS 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 = 0removes 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/ordersresponses now include three timing-breadcrumb fields:edge_received_at_ns,sequencer_committed_at_ns,engine_applied_at_ns(Unix ns;sequencer_committed_at_nscarries millisecond precision — Aeron cluster time ×1e6 — seeorders.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 inmarket_maker/PLAN.md §16.3. Workaround for V0.1 clients is cancel-and-replace.GET /v1/fees,GET /v1/risk_limits— V1 polish.Idempotency-Keyheader onPOST /v1/orders— V1 (existing backlog inorders.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}/candleswithresolution∈ 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 byX-Reporter-Key). - WebSocket:
GET /v1/wswith channelsorderbook_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_nsis sourced from Aeron Cluster'sonSessionMessagetimestamp, which is Unix milliseconds, not nanoseconds. The schema name is retained for backward compatibility with the cluster wire format; seemarket-data.md.