# ╔══════════════════════════════════════════════════════════════════════════╗ # ║ GENERATED FILE — DO NOT EDIT BY HAND. ║ # ║ Source of truth: the per-service markdown in docs/exchange/api/. ║ # ║ Regenerate: python3 docs/exchange/api/tools/gen_openapi.py ║ # ╚══════════════════════════════════════════════════════════════════════════╝ openapi: 3.1.0 info: title: Sphinx Exchange — Edge API version: 0.4.0 summary: Custodial, crypto-settled prediction-market exchange — public REST + WSS surface. description: 'The Edge API is the public surface of Sphinx Exchange. It speaks Kalshi-compatible vocabulary over REST and WebSocket; under the hood, every order is sequenced by an Aeron Cluster deterministic core, matched by an in-memory C++ engine, and audited into ClickHouse — see `docs/exchange/exchange_software_design.md` for the architecture. This spec covers V0 and V0.1 (in-process MockChain custody, single-node sequencer, operator-attested resolution authority). See `docs/exchange/api/README.md` for the human-readable docs, change process, and codegen instructions. This file is GENERATED from the per-service markdown in `docs/exchange/api/services/` by `tools/gen_openapi.py` — edit the markdown, not this file. The margin-era (derivatives) surface is included here: the margin `/v1/snapshot` block, `/v1/margin_info`, `/v1/insurance_fund`, `/v1/liquidation_metrics`, `/v1/contract_spec(s)`, the `reduce_only` / `post_only` order flags, and the mark / account WebSocket channels — behavior normative in `docs/exchange/exchange_derivatives_engine_spec.md` §14. Projection vs engine state: engine-state fields (balances, `signed_qty`, the `margin` block, marks, `last_settlement_price` = the SV-2 basis) reconcile bit-for-bit against the engine at one `engine_seq_applied`; the average entry price is an Edge PROJECTION off the fill tape (`GET /v1/fills`), never an engine field — do not conflate the two. EXTERNAL VARIANT: operator-only (`x-internal`) operations are omitted.' termsOfService: https://sphx.io/terms contact: name: Sphinx Exchange Engineering url: https://github.com/sphx-io email: api@sphx.io license: name: Proprietary identifier: LicenseRef-Sphinx-Proprietary servers: - url: https://api.staging.sphx.io description: Staging — LIVE (always-on demo venue; signup faucet enabled) - url: https://api.sphx.io description: Production (placeholder; not yet deployed) - url: http://127.0.0.1:18201 description: Local development (services brought up via `make up`) tags: - name: Accounts description: Account registration, profile lookup. - name: Credentials description: API key management. Customers may issue, list, and revoke their own credentials. - name: Auth description: Email+password sessions for outside users. Signup/login mint a short-lived session credential and return the same `.` bearer API keys use; logout revokes it. - name: Orders description: Place, cancel, and list open orders. - name: Market Data description: Instruments, order books, balances, positions, fills. - name: Candles description: OHLCV candle bars for charting (multiple resolutions). - name: Custody description: Deposit address allocation and withdrawal lifecycle. - name: Margin description: Margin sub-account cash transfers and margin-state projections. - name: Instrument description: Instrument lifecycle (read) and reporter resolution submission. - name: WebSocket description: Real-time push channels. Documented as one operation for codegen visibility; the wire protocol is in `docs/exchange/api/services/websockets.md`. - name: Operations description: Service health and metrics, served on a separate port (8080 in dev). security: - {} externalDocs: description: Human-readable API documentation url: https://github.com/sphx-io/docs/blob/main/exchange/api/README.md components: securitySchemes: BearerAuth: type: http scheme: bearer description: 'Customer-facing authentication. Use the `plaintext_key` returned by `POST /v1/accounts` or `POST /v1/credentials` as the bearer token. Tokens have the shape `.` where the prefix is the credential''s public identifier (used as a database lookup key) and the suffix is a high-entropy secret. Edge validates by looking up the credential by prefix and verifying SHA-256 of the suffix against the stored hash. ' ReporterKey: type: apiKey in: header name: X-Reporter-Key description: 'Reporter authentication for `/v1/instrument/resolution/*` (submit + preview). Dev value is the literal string `dev-reporter` (configurable via `EDGE_REPORTER_API_KEY`). V0 is a single shared secret; V1 introduces reporter quorum (each reporter has its own key + stake). ' parameters: PathInstrumentID: name: instrument_id in: path required: true schema: type: integer format: int64 minimum: 1 description: The instrument ID. PathOrderID: name: order_id in: path required: true schema: type: integer format: int64 minimum: 1 description: The order ID. PathWithdrawalID: name: withdrawal_id in: path required: true schema: type: integer format: int64 minimum: 1 description: The withdrawal ID. PathCredentialID: name: credential_id in: path required: true schema: type: integer format: int64 minimum: 1 description: The credential ID. responses: BadRequest: description: 'Request was malformed or failed validation. The `error.code` field carries the machine-readable reason; common values include `invalid_request`, `invalid_json`, `invalid_order_id`, `invalid_instrument_id`, `invalid_credential_id`, `invalid_resolution`, and operation-specific codes documented per endpoint. ' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' examples: invalid_request: value: error: code: invalid_request message: amount must be positive correlation_id: 8c2c0e9a-3... Unauthorized: description: Missing or invalid `Authorization` header. content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' examples: unauthorized: value: error: code: unauthorized message: missing or invalid bearer token correlation_id: 8c2c0e9a-3... Forbidden: description: Authentication succeeded but the action is not permitted. content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' examples: forbidden: value: error: code: forbidden message: admin key required correlation_id: 8c2c0e9a-3... NotFound: description: The named resource does not exist (or belongs to another account). content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' Conflict: description: 'The request conflicts with existing state. The `error.code` field carries the machine-readable reason (e.g. `email_taken` on signup). ' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' examples: email_taken: value: error: code: email_taken message: an account with this email already exists correlation_id: 8c2c0e9a-3... RateLimited: description: Too many attempts inside the fixed rate-limit window (10 attempts / 5 minutes per key). Retry in a few minutes. content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' examples: rate_limited: value: error: code: rate_limited message: too many attempts; retry in a few minutes correlation_id: 8c2c0e9a-3... InternalError: description: Unexpected server error. Includes a `correlation_id`; report it when filing a bug. content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' ServiceUnavailable: description: A required downstream (matching engine, ClickHouse, custody, instrument service) is unreachable. The request was not processed; retry with backoff. content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' examples: CreateAccountResponse: summary: A freshly-registered account value: data: account: account_id: 7 state: active entitlement_mask: 15 default_subaccount: 0 handle: alice created_at_ns: 1779108879000000000 initial_credential: credential_id: 7 account_id: 7 prefix: 0167a3... entitlement_mask: 15 label: initial created_at_ns: 1779108879100000000 plaintext_key: 0167a3....6f4c8d99a4b2e... correlation_id: a17d-… PlaceOrderRested: summary: Limit order that rests on the book value: data: order_id: 42 sequencer_correlation_id: 17 status: accepted_rested filled_count: 0 resting_count: 100 side: 'yes' action: buy order_type: limit price_pips: 5500 count: 100 edge_received_at_ns: 1779000000123456 sequencer_committed_at_ns: 1779000000456789 engine_applied_at_ns: 1779000000789012 correlation_id: a17d-… PlaceOrderFullyFilled: summary: Limit order that fully matches value: data: order_id: 43 sequencer_correlation_id: 18 status: accepted_fully_filled filled_count: 100 resting_count: 0 side: 'yes' action: sell order_type: limit price_pips: 5500 count: 100 edge_received_at_ns: 1779000000123456 sequencer_committed_at_ns: 1779000000456789 engine_applied_at_ns: 1779000000789012 correlation_id: a17d-… schemas: Envelope: type: object description: 'Every JSON response wraps its payload in this envelope. Successful responses set `data` to the operation''s payload; error responses use `ErrorEnvelope` instead. `correlation_id` echoes the request''s correlation ID and is the key reference for debugging — include it in any support ticket. ' required: - correlation_id properties: correlation_id: type: string description: Per-request correlation ID (UUID v4 unless the client overrode it via the `X-Correlation-ID` header). examples: - 8c2c0e9a-3...-... ErrorEnvelope: type: object required: - error - correlation_id properties: error: $ref: '#/components/schemas/Error' correlation_id: type: string examples: - 8c2c0e9a-3...-... Error: type: object required: - code - message properties: code: type: string description: Machine-readable error category. See the docs `error code` table for all values. examples: - invalid_request - unauthorized - insufficient_funds message: type: string description: Human-readable error description, safe to surface to operators. details: description: Optional, operation-specific structured detail. AccountState: type: string description: "Account lifecycle state.\n - `pending` — account exists, awaiting activation (KYC,\ \ etc.)\n - `active` — full trading + deposit + withdrawal\n - `trade_frozen` — withdrawals\ \ allowed, no new trades\n - `withdraw_frozen` — trades allowed, no new withdrawals\n - `fully_frozen`\ \ — read-only\n - `closed` — terminal; account is gone\n" enum: - pending - active - trade_frozen - withdraw_frozen - fully_frozen - closed OrderSide: type: string description: "Which side of the binary market this order is on.\n - `yes` — bid/ask on the YES\ \ contract\n - `no` — bid/ask on the NO contract (a SELL YES is also expressible as a BUY NO\ \ at 10000 - price)\n" enum: - 'yes' - 'no' InstrumentState: type: string description: "Lifecycle state of an instrument (engine view).\n - `trading` — open for orders\n\ \ - `pending` — closed for new orders, awaiting resolution\n - `reported` — outcome submitted\ \ (V1: dispute window open)\n - `disputed` — under review (V1)\n - `final` — resolved + settled\n" enum: - trading - pending - reported - disputed - final Instrument: type: object description: Full instrument metadata as managed by the Instrument service. required: - instrument_id - resolution_criterion - max_payoff_pips - state - created_at_ns properties: instrument_id: type: integer format: int64 resolution_criterion: type: string description: Human-readable criterion the reporter attests against ("Will event X happen by date Y?"). closes_at_ns: type: integer format: int64 description: Trading closes at this timestamp; 0 means manually advanced by operator. max_payoff_pips: type: integer format: int32 description: Maximum payoff per contract in pips. Binary YES/NO uses 10000 (= $1.00). state: type: integer format: int32 description: Numeric lifecycle state (0=LISTED, 1=TRADING, 2=PENDING, 3=REPORTED, 4=DISPUTED, 5=FINAL). minimum: 0 maximum: 5 resolved_outcome_pips: type: integer format: int32 description: Set once FINAL. 10000 = YES outcome, 0 = NO outcome. created_at_ns: type: integer format: int64 handle: type: string DepositAddress: type: object required: - account_id - asset_id - chain_id - address - created_at_ns properties: account_id: type: integer format: int64 asset_id: type: integer format: int32 chain_id: type: integer format: int32 address: type: string description: Chain-native address (Solana base58 in V1+). created_at_ns: type: integer format: int64 WithdrawalState: type: string description: "Withdrawal lifecycle.\n - `pending_finality` — accepted by Edge, awaiting custody-coordinator\ \ pickup\n - `broadcast` — chain transaction submitted, awaiting confirmation\n - `finalized`\ \ — confirmed on-chain\n - `failed` — terminal failure (insufficient funds at signer, RPC reject,\ \ etc.).\n Defined terminal state, but NEVER reached in V0/V0.1 (the in-process MockChain\n\ \ always succeeds and finalizes instantly); real failure paths arrive with Solana\n custody\ \ in V1.7.\n" enum: - pending_finality - broadcast - finalized - failed CreateWithdrawalRequest: type: object required: - amount - address properties: asset_id: type: integer format: int32 default: 1 description: Defaults to 1 (USDC). amount: type: integer format: int64 minimum: 1 description: Atomic units (USDC has 6 decimals). address: type: string description: Destination chain address. Withdrawal: type: object required: - withdrawal_id - account_id - asset_id - amount - state - submitted_at_ns properties: withdrawal_id: type: integer format: int64 account_id: type: integer format: int64 asset_id: type: integer format: int32 chain_id: type: integer format: int32 amount: type: integer format: int64 destination_address: type: string state: $ref: '#/components/schemas/WithdrawalState' chain_txn_sig: type: string description: Chain transaction signature (set once broadcast). submitted_at_ns: type: integer format: int64 broadcast_at_ns: type: integer format: int64 finalized_at_ns: type: integer format: int64 sequencer_correlation_id: type: integer format: int64 status: type: string description: Convenience alias for `state` returned only on the initial POST response (`pending_finality`). Account: type: object required: - account_id - state - entitlement_mask - default_subaccount - created_at_ns properties: account_id: type: integer format: int64 state: $ref: '#/components/schemas/AccountState' entitlement_mask: type: integer format: int64 description: "Bitmask of entitlements granted to this account's credentials. Bits:\n 1 (TradeEnabled),\ \ 2 (WithdrawEnabled), 4 (DepositEnabled), 8 (APITrading).\nV0 default is 15 (all four).\n" default_subaccount: type: integer format: int32 default: 0 handle: type: string description: Optional display handle chosen at registration. created_at_ns: type: integer format: int64 Credential: type: object required: - credential_id - account_id - prefix - entitlement_mask - created_at_ns properties: credential_id: type: integer format: int64 account_id: type: integer format: int64 prefix: type: string description: Public credential identifier (the part before the `.` in the Bearer token). entitlement_mask: type: integer format: int64 label: type: string description: Operator-chosen label for this credential (e.g. "frontend", "backtest-bot"). created_at_ns: type: integer format: int64 expires_at_ns: type: integer format: int64 description: Expiry timestamp (0 = no expiry). plaintext_key: type: string description: '**The full Bearer token, returned exactly once at creation time.** Subsequent calls to `GET /v1/credentials` will NOT include this field. Store immediately. ' CreateAccountRequest: type: object properties: handle: type: string description: Optional display handle. CreateAccountResponse: type: object required: - account - initial_credential properties: account: $ref: '#/components/schemas/Account' initial_credential: $ref: '#/components/schemas/Credential' CreateCredentialRequest: type: object properties: label: type: string description: Display label for the credential. entitlement_mask: type: integer format: int64 description: Bitmask. Defaults to the calling credential's mask if omitted. SignupRequest: type: object required: - email - password properties: email: type: string format: email description: Login identifier; lowercased and trimmed server-side. One account per email. password: type: string minLength: 10 maxLength: 128 description: Stored as argon2id (PHC format); never returned. handle: type: string description: Optional display handle. Defaults to the email's local part. LoginRequest: type: object required: - email - password properties: email: type: string format: email password: type: string SessionResponse: type: object required: - account - session_token - expires_at_ns properties: account: $ref: '#/components/schemas/Account' session_token: type: string description: 'The session bearer for `Authorization: Bearer` — same `.` format as API keys. **Returned exactly once**; store it immediately. ' expires_at_ns: type: integer format: int64 description: Session expiry timestamp (nanoseconds since epoch). Sessions live 7 days. faucet_uusdc: type: integer format: int64 description: 'Signup only, faucet-enabled venues only (dev/staging): the µUSDC credit being sequenced asynchronously — balances appear within a few seconds. Omitted when the faucet is disabled and on login. ' LogoutResponse: type: object required: - status properties: status: type: string enum: - logged_out PreviewResolutionResponse: type: object required: - instrument - has_index_value - note properties: instrument: $ref: '#/components/schemas/Instrument' has_index_value: type: boolean description: Whether the price component has a journaled selected index value for this instrument. index_value_uusdc: type: integer format: int64 description: The last selected index value (micro-USDC). Present when has_index_value. index_slot_ts_ns: type: integer format: int64 description: The slot instant the value was selected for (epoch-aligned Unix ns). index_published_at_ns: type: integer format: int64 description: Wall-clock journal time of the publication (diagnostic). staleness_ns: type: integer format: int64 description: now − index_slot_ts_ns at preview time. Advisory staleness signal. note: type: string description: Advisory disclaimer — the engine derives b′ outcomes authoritatively from sequenced state. MarginMetadata: type: object description: 'Identity/display record for a listed margined instrument, recorded at listing time. base/quote/expiry_date derive from price_feed_id; symbol is unique venue-wide. Economics live in the ContractSpec projection. ' required: - instrument_id - symbol - display_name - base - quote - product_class - price_feed_id - listed_at_ns properties: instrument_id: type: integer format: int64 symbol: type: string description: Unique display symbol, e.g. BTC-USD-PERP / BTC-USD-20260926. display_name: type: string base: type: string description: Base asset from the feed grammar, e.g. "btc". quote: type: string description: Quote asset from the feed grammar, e.g. "usd". product_class: type: integer description: 0 = perpetual, 1 = expiring future. price_feed_id: type: string description: The data-oracle index feed named at listing (manifest record). expiry_date: type: string description: YYYYMMDD from a fut-dated feed id; omitted otherwise. Display hint — the engine's expiry_datetime_utc_ns is authoritative. listed_at_ns: type: integer format: int64 SubmitResolutionRequest: type: object required: - instrument_id - outcome_pips properties: instrument_id: type: integer format: int64 outcome_pips: type: integer format: int32 enum: - 0 - 10000 description: 10000 = YES outcome, 0 = NO outcome. evidence_ref: type: string description: URL or hash pointing at supporting evidence. reporter_id: type: integer format: int64 description: V0 ignores this (single shared reporter key); V1 uses it for quorum. SubmitResolutionResponse: type: object required: - instrument - submission_id properties: instrument: $ref: '#/components/schemas/Instrument' submission_id: type: integer format: int64 InstrumentSnapshot: type: object required: - instrument_id - state properties: instrument_id: type: integer format: int64 state: $ref: '#/components/schemas/InstrumentState' Orderbook: type: object required: - instrument_id - yes_bids - no_bids properties: instrument_id: type: integer format: int64 yes_bids: type: array items: $ref: '#/components/schemas/OrderbookLevel' no_bids: type: array items: $ref: '#/components/schemas/OrderbookLevel' yes_asks: type: array description: Side-native margined SELLS (signed ticks, best ask first). Present on margined books only; event-class books keep the §11.9 no_bids complement form (POWER v12). items: $ref: '#/components/schemas/OrderbookLevel' OrderbookLevel: type: object required: - price_pips - count properties: price_pips: type: integer format: int32 minimum: 1 maximum: 9999 count: type: integer format: int32 minimum: 1 CandleResolution: type: string enum: - 1m - 5m - 15m - 1h - 1d Candle: type: object required: - t - o - h - l - c - v properties: t: type: integer format: int64 description: Bar start time (Unix seconds). o: type: integer format: int32 description: Open price (pips). h: type: integer format: int32 description: High price (pips). l: type: integer format: int32 description: Low price (pips). c: type: integer format: int32 description: Close price (pips). v: type: integer format: int64 description: Volume (contracts). closed: type: boolean description: '`true` when the bar window has elapsed and values are final. Omitted (or `false`) for the latest in-flight bar, which may update on the `candles` WSS channel. ' CandleSeries: type: object required: - instrument_id - resolution - side - bars properties: instrument_id: type: integer format: int64 resolution: $ref: '#/components/schemas/CandleResolution' side: $ref: '#/components/schemas/OrderSide' bars: type: array items: $ref: '#/components/schemas/Candle' VenueHealthResponse: type: object required: - status - books_two_sided - last_fill_age_s - disk_used_pct properties: status: type: string enum: - ok - unhealthy reasons: type: array description: Present when unhealthy — the failing checks. items: type: string enum: - books_one_sided - tape_stale - disk_pressure books_two_sided: type: integer description: Count of margined books currently quoted on both sides. last_fill_age_s: type: integer format: int64 description: Seconds since the venue's most recent fill. disk_used_pct: type: integer description: Root-disk usage percent on the venue host. OrderAction: type: string enum: - buy - sell OrderType: type: string enum: - limit - market TimeInForce: type: string description: "Order lifetime.\n - `gtc` — Good Till Cancelled (default)\n - `ioc` — Immediate\ \ Or Cancel (match what you can, cancel the rest)\n - `fok` — Fill Or Kill (match the whole thing\ \ or reject it)\n" enum: - gtc - ioc - fok OrderStatus: type: string description: "Engine outcome for an order.\n - `accepted_fully_filled` — entire quantity matched\n\ \ - `accepted_rested` — none matched immediately; on the book\n - `accepted_ioc_canceled` —\ \ partial match (or none) and IOC cancelled the rest\n - `rejected` — engine refused (see `reject_reason`)\n" enum: - accepted_fully_filled - accepted_rested - accepted_ioc_canceled - rejected OrderRejectReason: type: string description: 'Engine reject reason. The HTTP status mapping is documented per endpoint — balance/market reasons go to 422, account-state to 403, instrument-lifecycle to 409, validation to 400. ' enum: - invalid_price - invalid_quantity - invalid_max_cost - unknown_instrument - fok_would_not_fully_fill - max_cost_blocks_any_fill - market_no_liquidity - insufficient_funds - invalid_api_fields - stp_aborted - account_not_active - account_trade_frozen - instrument_halted - kill_switch_active - margined_requires_margin_sub - event_on_margin_sub - margined_limit_only - no_mark_yet - too_many_open_orders - account_notional_cap - venue_oi_cap - outside_price_band - reduce_only_would_increase - post_only_would_cross - instrument_reduce_only - circuit_breaker_halt - daily_notional_cap - unsupported_order_flags - fund_floor_open_block - unwind_rate_limit - one_sided_oi_cap - cancel_only_window - instrument_delist_pending STPQualifier: type: string description: 'Self-trade prevention. Only `cancel_newest` is supported in V0. ' enum: - cancel_newest PlaceOrderRequest: type: object required: - instrument_id - side - action - order_type - count properties: instrument_id: type: integer format: int64 sub_account_id: type: integer format: int32 default: 0 client_order_id: type: string maxLength: 64 description: Optional client-supplied idempotency key. A retry with the same key (within the ~15-minute Edge dedup window) replays the ORIGINAL ack instead of re-placing; the same key with a DIFFERENT order body is rejected 409 `client_order_id_reuse`. After an engine-ack timeout the outcome is unknown and a same-key retry gets 409 `order_outcome_unknown` — query GET /v1/orders before retrying under a new key. The window clears on an Edge restart (documented at-most-once semantics; the engine itself never double-fills a sequenced order). side: $ref: '#/components/schemas/OrderSide' action: $ref: '#/components/schemas/OrderAction' order_type: $ref: '#/components/schemas/OrderType' tif: $ref: '#/components/schemas/TimeInForce' price_pips: type: integer format: int32 minimum: 1 maximum: 9999 description: Required for `order_type=limit`. Integer pips (1–9999 = 0.0001–0.9999). count: type: integer format: int32 minimum: 1 description: Number of contracts. max_cost: type: integer format: int64 description: 'Required for `order_type=market` + `action=buy`. Forbidden for `order_type=market` + `action=sell`. Atomic units of the settlement asset (USDC has 6 decimals). ' stp_qualifier: $ref: '#/components/schemas/STPQualifier' reduce_only: type: boolean default: false description: 'RC-3 (B4): admit only if the order strictly reduces the position; fill-time auto-cancel. Margin-era.' post_only: type: boolean default: false description: 'RC-3 (B4): reject if the order would cross (take) on entry. Margin-era.' PlaceOrderResponse: type: object required: - order_id - correlation_id - status - filled_count - resting_count - side - action - order_type - count - edge_received_at_ns - sequencer_committed_at_ns - engine_applied_at_ns properties: order_id: type: integer format: int64 sequencer_correlation_id: type: integer format: int64 description: Sequencer-assigned correlation for this submission (distinct from the envelope's HTTP `correlation_id`). client_order_id: type: string description: Echo of the request's idempotency key, when one was supplied. status: $ref: '#/components/schemas/OrderStatus' filled_count: type: integer format: int32 resting_count: type: integer format: int32 reject_reason: $ref: '#/components/schemas/OrderRejectReason' side: $ref: '#/components/schemas/OrderSide' action: $ref: '#/components/schemas/OrderAction' order_type: $ref: '#/components/schemas/OrderType' price_pips: type: integer format: int32 count: type: integer format: int32 edge_received_at_ns: type: integer format: int64 description: (V0.1 T1-5) PTP Unix ns at which Edge first read this order off the wire (post-TLS, pre-routing). Subtract from `sequencer_committed_at_ns` for the network+Edge+commit span. Spec §19.0.1. sequencer_committed_at_ns: type: integer format: int64 description: '(V0.1 T1-5) Unix ns at which the sequencer assigned this order its global sequence number and committed it to the cluster log. NOTE: sourced from the Aeron Cluster commit time, which is Unix MILLISECONDS, normalised to ns by ×1e6 — so this value carries millisecond precision (the same underlying clock as the documented `Fill.ts_ns` artifact), unlike the nanosecond-precise edge_received_at_ns / engine_applied_at_ns.' engine_applied_at_ns: type: integer format: int64 description: (V0.1 T1-5) PTP Unix ns at which the matching engine applied this order and produced the outcome. `engine_applied_at_ns − sequencer_committed_at_ns` is the dispatch+apply span. CancelOrderResponse: type: object required: - order_id - status - correlation_id properties: order_id: type: integer format: int64 status: type: string enum: - canceled sequencer_correlation_id: type: integer format: int64 OpenOrder: type: object required: - order_id - instrument_id - sub_account_id - api_side - action - order_type - tif - price_pips - quantity properties: order_id: type: integer format: int64 instrument_id: type: integer format: int64 sub_account_id: type: integer format: int32 api_side: $ref: '#/components/schemas/OrderSide' action: $ref: '#/components/schemas/OrderAction' order_type: $ref: '#/components/schemas/OrderType' tif: $ref: '#/components/schemas/TimeInForce' price_pips: type: integer format: int32 quantity: type: integer format: int32 Balance: type: object required: - asset_id - total - held - available properties: asset_id: type: integer format: int32 description: V0 only `1` (USDC). total: type: integer format: int64 description: total = available + held (atomic units). held: type: integer format: int64 description: Locked by open orders + pending withdrawals. available: type: integer format: int64 Position: type: object description: 'API-5 signed-qty migration. `signed_qty` is the shared field. Margined positions add `last_settlement_price` (engine state, the SV-2 basis) and omit the legacy yes/no. Event contracts DUAL-SERVE `yes_position` / `no_position` alongside the YES-collapsed `signed_qty` for one client release cycle. ' required: - instrument_id - sub_account_id - signed_qty properties: instrument_id: type: integer format: int64 sub_account_id: type: integer signed_qty: type: integer format: int64 last_settlement_price: type: integer format: int64 yes_position: type: integer format: int64 no_position: type: integer format: int64 Fill: type: object required: - event_seq - ts_ns - instrument_id - maker_order_id - taker_order_id - your_side - your_action - price_pips - count - correlation_id properties: event_seq: type: integer format: int64 description: Cluster sequence number for this fill — monotonic, ordered, deduplication key. ts_ns: type: integer format: int64 description: Aeron Cluster timestamp at sequence (caveat - milliseconds, not nanoseconds — schema-name historical artefact). instrument_id: type: integer format: int64 maker_order_id: type: integer format: int64 taker_order_id: type: integer format: int64 your_side: type: string enum: - maker - taker description: Which side of the trade you were on. your_action: type: string enum: - buy - sell description: Your action from your perspective (after YES/NO normalisation). price_pips: type: integer format: int32 count: type: integer format: int32 sequencer_correlation_id: type: integer format: int64 FillsPage: type: object required: - fills - next_cursor properties: fills: type: array items: $ref: '#/components/schemas/Fill' next_cursor: type: - string - 'null' description: Pass as `cursor` for the next (older) page; null on the last page. Snapshot: type: object description: Atomic account snapshot at engine_seq_applied. Event-contract positions dual-serve yes/no; margined sub-accounts add the margin block. (API-1/API-5.) required: - engine_seq_applied - balances - positions - open_orders - pending_withdrawals properties: engine_seq_applied: type: integer format: int64 balances: type: array items: type: object required: - asset_id - total - held properties: asset_id: type: integer format: int64 total: type: integer format: int64 held: type: integer format: int64 positions: type: array items: $ref: '#/components/schemas/Position' open_orders: type: array items: type: object required: - order_id - instrument_id - side - action - price_pips - count - filled_count properties: order_id: type: integer format: int64 instrument_id: type: integer format: int64 side: type: string enum: - 'yes' - 'no' action: type: string enum: - buy - sell price_pips: type: integer count: type: integer format: int64 description: Remaining (un-filled) contract count of the resting order. filled_count: type: integer format: int64 description: Always 0 in V0.1 — the atomic engine snapshot does not carry partial-fill history. Use GET /v1/orders/{order_id} or GET /v1/fills for fills. pending_withdrawals: type: array items: type: object required: - withdrawal_id - asset_id - amount - state properties: withdrawal_id: type: integer format: int64 asset_id: type: integer format: int64 amount: type: integer format: int64 state: type: string enum: - pending description: 'Engine view. A withdrawal listed here has an active hold the engine has not yet seen finalized, so V0.1 always reports "pending". The full Custody lifecycle (the canonical WithdrawalState schema: pending_finality / broadcast / finalized / failed) is available via GET /v1/withdrawals/{id} — that''s the authoritative per-withdrawal view, separate from this engine-snapshot field.' margin: $ref: '#/components/schemas/MarginBlock' description: Present for margined sub-accounts; omitted for the fully-collateralized event-contract product. MarginBlock: type: object description: Per-sub-account margin block (API-1). All µUSDC int64. Derived but served at engine_seq_applied. required: - sub_account_id - equity - im_held - mm_required - free_collateral - margin_withdrawable - reserved_funding - positions properties: sub_account_id: type: integer equity: type: integer format: int64 im_held: type: integer format: int64 mm_required: type: integer format: int64 free_collateral: type: integer format: int64 margin_withdrawable: type: integer format: int64 reserved_funding: type: integer format: int64 positions: type: array items: type: object required: - instrument_id - qty - last_settlement_price - upnl_at_mark - mark_seq properties: instrument_id: type: integer format: int64 qty: type: integer format: int64 last_settlement_price: type: integer format: int64 upnl_at_mark: type: integer format: int64 mark_seq: type: integer format: int64 InsuranceFund: type: object required: - engine_seq_applied - balance_uusdc - flows properties: engine_seq_applied: type: integer format: int64 balance_uusdc: type: integer format: int64 next_cursor: type: - string - 'null' flows: type: array items: type: object required: - event_seq - ts - amount_uusdc - cause - instrument - mark_seq properties: event_seq: type: integer format: int64 ts: type: integer format: int64 amount_uusdc: type: integer format: int64 description: signed; + is an inflow to the fund cause: type: string enum: - penalty - unwind_gain - funding_dust - operator_topup - shortfall_cover - dispersal instrument: type: integer format: int64 mark_seq: type: integer format: int64 LiquidationMetrics: type: object required: - delay_hours - cutoff_ns - buckets properties: delay_hours: type: integer cutoff_ns: type: integer format: int64 buckets: type: array items: type: object required: - instrument_id - hour_start_ns - liquidated_notional_uusdc - count properties: instrument_id: type: integer format: int64 hour_start_ns: type: integer format: int64 liquidated_notional_uusdc: type: integer format: int64 count: type: integer format: int64 ContractSpecResponse: type: object required: - engine_seq_applied - manifest_hash - spec properties: engine_seq_applied: type: integer format: int64 manifest_hash: type: string spec: $ref: '#/components/schemas/ContractSpec' ContractSpec: type: object required: - instrument_id - tick_size_uusdc - product_class - im_bps - mmr_bps - funding_is_fee_free properties: instrument_id: type: integer format: int64 product_class: type: integer tick_size_uusdc: type: integer format: int64 basis_ticks: type: integer format: int64 im_bps: type: integer mmr_bps: type: integer limit_band_buy_bps: type: integer limit_band_sell_bps: type: integer fee_model: type: integer maker_fee_rate_bps: type: integer format: int32 taker_fee_rate_bps: type: integer format: int32 funding_is_fee_free: type: boolean has_price_domain: type: boolean description: POWER v12 — the signed price domain is configured (min 0 is a real bound when set). min_price_ticks: type: integer format: int32 description: Signed domain floor in ticks (real only under has_price_domain). max_price_ticks: type: integer format: int32 exposure_floor_uusdc: type: integer format: int64 description: Per-contract margin-exposure floor (0 = none; power_products_spec §4.2-2). funding_dollar_cap_daily_rate_bps: type: integer description: PERP-8 dollar-cap daily rate (0 = venue default 720). funding_dollar_cap_lookback_hours: type: integer description: PERP-8 rolling-low lookback (0 = venue default 24). MarginTransferRequest: type: object required: - direction - amount_uusdc properties: account_id: type: integer format: int64 description: Optional. When present it MUST equal the authenticated account — the session is the sole authority on whose margin moves. direction: type: string enum: - in - out description: '`in` = spot balance → margin sub-account; `out` = margin → spot.' amount_uusdc: type: integer format: int64 minimum: 1 description: Micro-USDC (1e-6 USDC), the API's standard integer money encoding. transfer_id: type: integer format: uint64 description: Optional client-chosen identity/idempotency token; generated by Edge when omitted. MarginTransferResponse: type: object required: - transfer_id - account_id - direction - amount_uusdc - status - sequencer_correlation_id properties: transfer_id: type: integer format: uint64 account_id: type: integer format: int64 direction: type: string enum: - in - out amount_uusdc: type: integer format: int64 status: type: string enum: - accepted description: Always `accepted` — accepted FOR SEQUENCING; the engine verdict is asynchronous. sequencer_correlation_id: type: integer format: int64 description: Engine-side correlation id — join to WS push events. paths: /v1/deposits/addresses: get: operationId: getDepositAddress tags: - Custody summary: Get (or allocate) a deposit address for the authenticated account. description: 'Returns the on-chain deposit address for the (asset, chain) pair, allocating one from the hot-wallet pool if no address has been issued yet. The same address is returned on subsequent calls — it''s a stable per-(account, asset, chain) identifier. V0 supports `asset=1` (USDC) and `chain=99` (mock chain). V1+ adds `chain=101` (Solana mainnet). ' security: - BearerAuth: [] parameters: - name: asset in: query required: false schema: type: integer format: int32 default: 1 description: Asset ID. V0 = 1 (USDC). - name: chain in: query required: false schema: type: integer format: int32 default: 99 description: Chain ID. V0 = 99 (mock); V1+ = 101 (Solana mainnet). responses: '200': description: Deposit address. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/DepositAddress' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/withdrawals: post: operationId: createWithdrawal tags: - Custody summary: Initiate a withdrawal to an off-platform address. description: 'Submits a withdrawal request. The account is debited synchronously (the amount moves from `available` to `held`); the on-chain broadcast happens asynchronously through the custody coordinator and worker pipeline. Monitor the withdrawal''s state via `GET /v1/withdrawals/{withdrawal_id}` — `pending_finality → broadcast → finalized`. Requires the account to be in a withdraw-capable state (`active` or `trade_frozen`). ' security: - BearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateWithdrawalRequest' examples: usdc_withdrawal: value: asset_id: 1 amount: 100000000 address: 9wfRgFJjN5gKx2dHnQq8VtFXc8VtFXqJZb7uHgPvBdSP responses: '202': description: Withdrawal accepted. The on-chain broadcast is asynchronous. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/Withdrawal' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': description: Account is not in a withdraw-capable state. content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/withdrawals/{withdrawal_id}: parameters: - $ref: '#/components/parameters/PathWithdrawalID' get: operationId: getWithdrawal tags: - Custody summary: Get the status of a withdrawal. description: 'Returns the full withdrawal record. `state` progresses through `pending_finality → broadcast → finalized` — the only path in V0/V0.1, where the in-process MockChain always succeeds and finalizes instantly. The `failed` terminal state is defined but never reached until real Solana custody (V1.7). ' security: - BearerAuth: [] responses: '200': description: Withdrawal record. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/Withdrawal' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': description: No such withdrawal — or it belongs to another account. content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/accounts: post: operationId: createAccount tags: - Accounts summary: Register a new account. description: 'Creates an account and issues an initial API credential. The `plaintext_key` in `initial_credential` is returned exactly once and cannot be retrieved later — store it immediately. Subsequent credentials can be issued via `POST /v1/credentials`. No authentication is required for this endpoint. ' security: [] requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/CreateAccountRequest' examples: minimal: summary: Anonymous account value: {} with_handle: summary: Account with handle value: handle: alice responses: '201': description: Account created. The response envelope's `data.initial_credential.plaintext_key` is the only place the key is ever returned in plaintext. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/CreateAccountResponse' examples: created: $ref: '#/components/examples/CreateAccountResponse' '400': $ref: '#/components/responses/BadRequest' '500': $ref: '#/components/responses/InternalError' /v1/accounts/me: get: operationId: getMe tags: - Accounts summary: Return the authenticated account. security: - BearerAuth: [] responses: '200': description: The account that owns the supplied API key. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/Account' '401': $ref: '#/components/responses/Unauthorized' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/auth/signup: post: operationId: signup tags: - Auth summary: Email+password signup; mints a session credential. description: 'Creates an account with entitlement mask 7 (no APITrading), stores the password (argon2id), and mints a session credential. `session_token` is a standard `.` bearer, returned exactly once, expiring after 7 days. On faucet-enabled venues (dev/staging) the signup credit is sequenced asynchronously; `faucet_uusdc` echoes the amount and is omitted when the faucet is off. Rate limit: 10 signups / 5 minutes per client IP. No authentication is required for this endpoint. ' security: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SignupRequest' examples: minimal: summary: Signup without a handle (defaults to the email local part) value: email: alice@example.com password: correct-horse-battery with_handle: summary: Signup with an explicit handle value: email: alice@example.com password: correct-horse-battery handle: alice responses: '200': description: Account created and session minted. `data.session_token` is the only place the session bearer is ever returned. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/SessionResponse' '400': $ref: '#/components/responses/BadRequest' '409': $ref: '#/components/responses/Conflict' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' /v1/auth/login: post: operationId: login tags: - Auth summary: Email+password login; mints a session credential. description: 'Verifies the password and mints a fresh 7-day session credential. Failed logins are uniformly `401 unauthorized` whether the email is unknown or the password wrong (equalized timing) — the endpoint is not an account-existence oracle. Rate limit: 10 attempts / 5 minutes per (client IP, email). No authentication is required for this endpoint. ' security: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/LoginRequest' examples: login: value: email: alice@example.com password: correct-horse-battery responses: '200': description: Session minted. `data.session_token` is the bearer; `faucet_uusdc` is never present on login. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/SessionResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/InternalError' /v1/auth/logout: post: operationId: logout tags: - Auth summary: Revoke the current session credential. description: 'Revokes the session credential authenticating this request; takes effect immediately. Only session credentials may call logout — an API key gets `400 invalid_request` (revoke keys via `DELETE /v1/credentials/{credential_id}`). ' security: - BearerAuth: [] responses: '200': description: Session revoked. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/LogoutResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '500': $ref: '#/components/responses/InternalError' /v1/credentials: post: operationId: createCredential tags: - Credentials summary: Issue a new API credential for the authenticated account. description: 'Mints a new credential with optional `label` and `entitlement_mask`. The mask defaults to the calling credential''s mask if omitted. As with `POST /v1/accounts`, `plaintext_key` is returned exactly once. ' security: - BearerAuth: [] requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/CreateCredentialRequest' examples: minimal: value: {} labelled_readonly: value: label: frontend-readonly entitlement_mask: 8 responses: '201': description: Credential created. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/Credential' '401': $ref: '#/components/responses/Unauthorized' '500': $ref: '#/components/responses/InternalError' get: operationId: listCredentials tags: - Credentials summary: List the authenticated account's credentials. description: Returns all non-revoked credentials for the account. `plaintext_key` is never returned by this endpoint. security: - BearerAuth: [] responses: '200': description: Array of credentials (without `plaintext_key`). content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: type: array items: $ref: '#/components/schemas/Credential' '401': $ref: '#/components/responses/Unauthorized' '500': $ref: '#/components/responses/InternalError' /v1/credentials/{credential_id}: parameters: - $ref: '#/components/parameters/PathCredentialID' delete: operationId: revokeCredential tags: - Credentials summary: Revoke a credential by ID. description: Revokes the named credential. The currently-authenticated credential may revoke itself; the revocation takes effect immediately. security: - BearerAuth: [] responses: '204': description: Credential revoked. '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '500': $ref: '#/components/responses/InternalError' /v1/instrument/instruments: get: operationId: listInstrumentsFull tags: - Instrument summary: List operator-defined instruments (read-only). description: Returns every instrument managed by the instrument service with full metadata (resolution criterion, closes_at, current state, resolved outcome if any). security: [] responses: '200': description: Array of instruments. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: type: array items: $ref: '#/components/schemas/Instrument' '500': $ref: '#/components/responses/InternalError' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/instrument/instruments/{instrument_id}: parameters: - $ref: '#/components/parameters/PathInstrumentID' get: operationId: getInstrumentFull tags: - Instrument summary: Get a single instrument's full metadata. security: [] responses: '200': description: Instrument metadata. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/Instrument' '400': $ref: '#/components/responses/BadRequest' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/instrument/resolution/submit: post: operationId: submitResolution tags: - Instrument summary: Submit a resolution outcome for an instrument (reporter only). description: 'Reporters submit the binary outcome (YES/NO) for an instrument in the `pending` lifecycle state. V0 is operator-attested with a single reporter key; V1 introduces reporter quorum and a dispute window. Authentication is via the `X-Reporter-Key` header (distinct from customer Bearer tokens). ' security: - ReporterKey: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SubmitResolutionRequest' examples: yes_outcome: summary: YES outcome value: instrument_id: 1 outcome_pips: 10000 evidence_ref: https://reporter.example.com/proof/1 reporter_id: 42 no_outcome: summary: NO outcome value: instrument_id: 1 outcome_pips: 0 responses: '200': description: Resolution recorded; instrument advances to FINAL. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/SubmitResolutionResponse' '400': $ref: '#/components/responses/BadRequest' '403': $ref: '#/components/responses/Forbidden' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/instrument/resolution/preview/{instrument_id}: get: operationId: previewResolution tags: - Instrument summary: Preview the venue's last selected index value before confirming a b′ resolution (reporter only). description: 'Advisory operator-confirm aid for index-resolved (b′) binaries: returns the last journaled selected index value (the price component''s slot-value journal) and its staleness. The engine derives the authoritative b′ outcome from its own sequenced index state — this preview never re-derives it. ' security: - ReporterKey: [] parameters: - $ref: '#/components/parameters/PathInstrumentID' responses: '200': description: Instrument state plus the last selected index value, when one exists. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/PreviewResolutionResponse' '400': $ref: '#/components/responses/BadRequest' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/instrument/margin_metadata: get: operationId: listMarginMetadata tags: - Instrument summary: Margined-instrument metadata registry — identity/display records. description: 'Public identity/display record for every listed margined instrument, recorded at listing time. base/quote/expiry_date derive from the listing''s price_feed_id; symbol/display_name are operator-overridable derivations and symbols are unique venue-wide. Join against GET /v1/contract_specs (economics) and GET /v1/instruments (trading state). ' responses: '200': description: Every margined instrument's metadata record. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: type: object required: - metadata properties: metadata: type: array items: $ref: '#/components/schemas/MarginMetadata' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/instruments: get: operationId: listInstruments tags: - Market Data summary: List instruments known to the matching engine. description: Returns a lightweight snapshot of every instrument the engine currently has loaded with its lifecycle state. For the operator-authored metadata (resolution criterion, closes_at, handle), use `/v1/instrument/instruments`. security: [] responses: '200': description: Array of instrument snapshots. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: type: array items: $ref: '#/components/schemas/InstrumentSnapshot' '500': $ref: '#/components/responses/InternalError' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/instruments/{instrument_id}: parameters: - $ref: '#/components/parameters/PathInstrumentID' get: operationId: getInstrument tags: - Market Data summary: Get an instrument snapshot. security: [] responses: '200': description: Engine snapshot of one instrument. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/InstrumentSnapshot' '400': $ref: '#/components/responses/BadRequest' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/instruments/{instrument_id}/orderbook: parameters: - $ref: '#/components/parameters/PathInstrumentID' get: operationId: getOrderbook tags: - Market Data summary: Get the order book for an instrument. description: 'Returns the full top-of-book ladder for both the YES and NO sides. Prices are integer pips (1–9999); counts are aggregate order counts at that price (not contract quantity totals). ' security: [] responses: '200': description: Order book snapshot. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/Orderbook' '400': $ref: '#/components/responses/BadRequest' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/instruments/{instrument_id}/candles: parameters: - $ref: '#/components/parameters/PathInstrumentID' get: operationId: getCandles tags: - Candles summary: Get OHLCV candles for charting. description: 'Returns time-bucketed OHLCV bars. The `from`/`to` window is in Unix **seconds**; either `from` + `to` OR `countback` + `to` should be supplied. Bars include a `closed` boolean — `true` means the bar window has elapsed and the values are final; the latest in-flight bar omits `closed` and may update on the `candles` WebSocket channel. ' security: [] parameters: - name: resolution in: query required: true schema: type: string enum: - 1m - 5m - 15m - 1h - 1d description: Candle bar resolution. - name: from in: query required: false schema: type: integer format: int64 description: Window start (Unix seconds, inclusive). Mutually exclusive with `countback`. - name: to in: query required: false schema: type: integer format: int64 description: Window end (Unix seconds, default `now`). - name: countback in: query required: false schema: type: integer minimum: 1 description: Number of bars to return ending at `to`. Mutually exclusive with `from`. - name: side in: query required: false schema: type: string enum: - 'yes' - 'no' default: 'yes' description: Which side's prints to chart. responses: '200': description: Candle series. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/CandleSeries' '400': $ref: '#/components/responses/BadRequest' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/instruments/{instrument_id}/trades: parameters: - $ref: '#/components/parameters/PathInstrumentID' get: operationId: getInstrumentTrades tags: - Market Data summary: (V0.1) Public trade tape for an instrument — paginated. description: 'Returns a paginated, anonymised projection of the `fills` table for the given instrument. Used by tape-driven analyses (the MM bot''s Kalman fair-value filter, third-party tape consumers). No account identifiers appear in the response. V0.1 reference: `market_maker/PLAN.md §16.2 item 6`; spec §12.1.7 ("Public trade wire format"). ' parameters: - in: query name: limit schema: type: integer minimum: 1 maximum: 1000 default: 100 - in: query name: cursor description: Opaque cursor returned by a previous call's `next_cursor`. schema: type: string - in: query name: since_event_seq description: Inclusive lower bound on `event_seq`. Mutually exclusive with `cursor`. schema: type: integer format: int64 responses: '200': description: Paginated trade tape. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: type: object required: - trades properties: trades: type: array items: type: object required: - event_seq - ts_ns - instrument_id - price_pips - count - taker_side properties: event_seq: type: integer format: int64 ts_ns: type: integer format: int64 instrument_id: type: integer format: int64 price_pips: type: integer minimum: 1 maximum: 9999 count: type: integer format: int64 taker_side: type: string enum: - buy - sell description: Aggressor action under YES-collapsed convention. next_cursor: type: - string - 'null' description: Pass back as `cursor` to retrieve the next page; null when no more pages. '400': $ref: '#/components/responses/BadRequest' '503': $ref: '#/components/responses/ServiceUnavailable' /healthz: get: operationId: healthz tags: - Operations summary: Liveness probe. description: Always returns 200 OK once the process has started, even before the API listener is bound. Served on the health port (8080 in dev). security: [] servers: - url: http://127.0.0.1:18101 description: Local development health port. responses: '200': description: Service is alive. content: text/plain: schema: type: string examples: - OK /readyz: get: operationId: readyz tags: - Operations summary: Readiness probe. description: Returns 200 OK only after the API listener is bound and accepting connections. Served on the health port. security: [] servers: - url: http://127.0.0.1:18101 description: Local development health port. responses: '200': description: Service is ready to accept traffic. '503': description: Service is up but not yet ready. /metrics: get: operationId: metrics tags: - Operations summary: Prometheus exposition. description: Scrape target for Prometheus. Served on the health port. Mostly Go-runtime metrics in V0; service-specific counters will be added in V1. security: [] servers: - url: http://127.0.0.1:18101 description: Local development health port. responses: '200': description: Prometheus exposition format. content: text/plain: schema: type: string /healthz/venue: get: operationId: getVenueHealth tags: - Operations summary: Venue-functional liveness probe (public). description: 'Judges functional venue health: two-sided books, fill-tape freshness, and disk headroom. `status` is `ok` only when every check passes; failing checks are named in `reasons`. Served on the main API port (unlike `/healthz`/`/readyz`/`/metrics`, which live on the operations health port) so external uptime monitors can probe it. ' security: [] responses: '200': description: Health verdict — HTTP 200 for BOTH healthy and unhealthy; monitors must match on `data.status`, not the status code. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/VenueHealthResponse' examples: healthy: summary: All checks green value: data: status: ok books_two_sided: 6 last_fill_age_s: 0 disk_used_pct: 34 correlation_id: f1e870f6-363e-457e-955a-e65dcef1f590 unhealthy: summary: Stale tape value: data: status: unhealthy reasons: - tape_stale books_two_sided: 6 last_fill_age_s: 40025 disk_used_pct: 31 correlation_id: 3897721d-941f-4712-9352-625de34abd54 /v1/orders: post: operationId: placeOrder tags: - Orders summary: Place an order. description: "Places a limit or market order on the YES or NO side of a prediction-market instrument.\n\ \n- `order_type=limit` requires `price_pips` (1–9999).\n- `order_type=market` with `action=buy`\ \ requires `max_cost` (atomic units of the\n settlement asset, e.g. USDC atomic).\n- `order_type=market`\ \ with `action=sell` must NOT set `max_cost`.\n\nSettlement asset is fixed to USDC in V0 (`asset_id=1`).\n\ \nThe synchronous response carries the engine's outcome: fully filled, rested on the book,\nIOC-cancelled\ \ with no rest, or rejected. Resting orders subsequently match other\nincoming orders; subscribe\ \ to the `fill` WSS channel to receive fills in real time.\n" security: - BearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PlaceOrderRequest' examples: limit_buy_yes: summary: Limit BUY YES @ 0.55 value: instrument_id: 1 side: 'yes' action: buy order_type: limit tif: gtc price_pips: 5500 count: 100 market_buy_yes: summary: Market BUY YES with USDC cap value: instrument_id: 1 side: 'yes' action: buy order_type: market max_cost: 50000000 count: 100 fok_sell_no: summary: FOK SELL NO @ 0.40 value: instrument_id: 1 side: 'no' action: sell order_type: limit tif: fok price_pips: 4000 count: 50 responses: '200': description: 'Order processed by the engine. The `status` field describes the outcome: `accepted_fully_filled`, `accepted_rested`, `accepted_ioc_canceled`, or `rejected`. A `rejected` outcome is still returned as HTTP 200 when accompanied by a wire reject reason — see §`reject_reason` for the inferred HTTP status mapping when the reject promotes to 400/403/409/422 at the boundary. ' content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/PlaceOrderResponse' examples: rested: $ref: '#/components/examples/PlaceOrderRested' fully_filled: $ref: '#/components/examples/PlaceOrderFullyFilled' '400': description: Validation failed or wire-reject promoted to client error. Common error codes — `invalid_request`, `invalid_json`, `invalid_price`, `invalid_quantity`, `invalid_max_cost`, `unknown_instrument`, `invalid_api_fields`. content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': description: 'Account is not in a tradable state. Error codes — `account_not_active`, `account_trade_frozen`. ' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '409': description: 'Instrument lifecycle or self-trade prevention blocked the order. Error codes — `instrument_halted`, `stp_aborted`. Also the idempotency conflicts (audit F2): `client_order_id_reuse` (same key, different body) and `order_outcome_unknown` (same key while the original is in flight or timed out — query GET /v1/orders first).' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '422': description: 'Reject reasons that map to "we understood the request but cannot execute it against the current book/balance". Error codes — `insufficient_funds`, `fok_would_not_fully_fill`, `max_cost_blocks_any_fill`, `market_no_liquidity`. ' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '504': description: Engine did not acknowledge the order within the deadline (default 2 s). Treat as unknown — the order may or may not have been sequenced. content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' get: operationId: listOpenOrders tags: - Orders summary: List the authenticated account's open (resting) orders. description: Returns every order currently resting on a book that belongs to the calling account, across all instruments. security: - BearerAuth: [] responses: '200': description: Array of open orders. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: type: array items: $ref: '#/components/schemas/OpenOrder' '401': $ref: '#/components/responses/Unauthorized' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/orders/{order_id}: parameters: - $ref: '#/components/parameters/PathOrderID' get: operationId: getOrderByID tags: - Orders summary: (V0.1) Look up an order by ID — including terminal state. description: 'Returns the order''s full lifecycle state regardless of whether it is still resting, has been filled, cancelled, rejected, or partially filled. The order must belong to the calling account; foreign accounts return 403. V0/V0.1 reference: `market_maker/PLAN.md §16.2 item 3`. Terminal orders are served from the Audit Indexer (ClickHouse `orders` table); resting orders are served from the Trading Engine''s live view. ' security: - BearerAuth: [] responses: '200': description: Order found; full lifecycle state returned. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: type: object required: - order_id - status - instrument_id - side - action - order_type - price_pips - count - filled_count - resting_count properties: order_id: type: integer format: int64 status: type: string enum: - resting - partially_filled - filled - canceled - rejected instrument_id: type: integer format: int64 side: type: string enum: - 'yes' - 'no' action: type: string enum: - buy - sell order_type: type: string enum: - limit - market price_pips: type: integer minimum: 1 maximum: 9999 count: type: integer format: int64 filled_count: type: integer format: int64 resting_count: type: integer format: int64 first_seen_at_ns: type: integer format: int64 terminal_at_ns: type: - integer - 'null' format: int64 reject_reason: type: - string - 'null' sequencer_correlation_id: type: integer format: int64 '401': $ref: '#/components/responses/Unauthorized' '403': description: Order belongs to a different account. content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '404': description: Order ID not known to either the engine or the audit indexer. content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '503': $ref: '#/components/responses/ServiceUnavailable' delete: operationId: cancelOrder tags: - Orders summary: Cancel a resting order by ID. description: 'Cancels the named order. The order must belong to the calling account; cancelling someone else''s order returns 403. Idempotent — cancelling an already-cancelled or already-filled order returns 404. ' security: - BearerAuth: [] responses: '200': description: Cancellation processed. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/CancelOrderResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': description: 'Account state forbids cancellation, or the order belongs to another account. Error codes — `account_not_active`, `cancel_rejected` with reason `wrong_account`. ' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '404': description: Order not found (unknown ID, already cancelled, or already filled). content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '504': description: Engine did not acknowledge within deadline. content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' /v1/orders/cancel_all: post: operationId: cancelAll tags: - Orders summary: (V0.1) Cancel all of the calling account's resting orders on one instrument. description: "Cancels every resting order the calling account holds on a single\n`instrument_id`.\ \ **`instrument_id` is REQUIRED** — V0.1 mass cancel is deliberately\nper-instrument; there is\ \ no all-instruments / account-wide kill form, and it does\n**not** enter a kill-switch state\ \ (spec §6.6).\n\nBacked by a single `MassCancel` SBE event. The sweep itself is **asynchronous**:\n\ a deep book may take a while to drain, so this endpoint returns **202 Accepted**\nimmediately\ \ rather than blocking. The engine then, in one sequenced apply pass:\n\n - emits one `OrderCanceled`\ \ per affected order — each lands in the audit `orders`\n table (so `GET /v1/orders/{order_id}`\ \ reflects the cancellation) and drives the\n public `orderbook_delta` WS channel; then\n \ \ - emits a terminating `MassCancelComplete`, surfaced on the authenticated\n **`account`**\ \ WS channel as a `mass_cancel_complete` event carrying the\n authoritative `canceled_count`.\n\ \nCorrelate the 202 response with the WS completion event via the shared\n`correlation_id`. `estimated_count`\ \ is a best-effort hint taken from the live engine\nsnapshot at request time; the WS event carries\ \ the actual count.\n\nV0.1 reference: `market_maker/PLAN.md §16.2 item 5`.\n" security: - BearerAuth: [] requestBody: required: true content: application/json: schema: type: object required: - instrument_id properties: instrument_id: type: integer format: int64 description: REQUIRED. The instrument whose resting orders (for this account) are cancelled. responses: '202': description: Mass-cancel sequenced; per-order cancels + completion arrive via WS. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: type: object required: - status - instrument_id - correlation_id - estimated_count properties: status: type: string enum: - accepted description: Always "accepted" — the sweep runs asynchronously. instrument_id: type: integer format: int64 sequencer_correlation_id: type: integer format: int64 description: Join key for the mass_cancel_complete event on the account WS channel. estimated_count: type: integer format: int64 description: Best-effort count from the live snapshot; the WS completion event carries the authoritative canceled_count. '400': description: Missing or invalid instrument_id. content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '401': $ref: '#/components/responses/Unauthorized' '403': description: Account state forbids cancellation (requires an active account). content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' /v1/snapshot: get: operationId: getAccountSnapshot tags: - Account summary: (V0.1) Atomic per-account snapshot of balances + positions + open orders. description: 'Returns balances, positions, open orders, and pending withdrawals for the calling account — all guaranteed to reflect the same `engine_seq_applied`. Internally serves from the Trading Engine''s `SnapshotState(at_seq)` gRPC, filtered to the calling account. Eliminates the multi-poll race that separate calls to `/v1/balances`, `/v1/positions`, `/v1/orders` would otherwise produce. V0.1 reference: `market_maker/PLAN.md §16.2 item 1`; spec §11.9.2. ' security: - BearerAuth: [] responses: '200': description: Atomic snapshot returned. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/Snapshot' '401': $ref: '#/components/responses/Unauthorized' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/balances: get: operationId: getBalances tags: - Market Data summary: Get the authenticated account's balances. description: 'Returns one row per asset. `total = available + held`. `held` is the portion locked against open orders or pending withdrawals. ' security: - BearerAuth: [] responses: '200': description: Array of balance rows. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: type: array items: $ref: '#/components/schemas/Balance' '401': $ref: '#/components/responses/Unauthorized' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/positions: get: operationId: getPositions tags: - Market Data summary: Get the authenticated account's positions. description: 'Returns one row per (instrument_id, sub_account_id). YES and NO positions are tracked separately; only one side will typically be non-zero for a given instrument. ' security: - BearerAuth: [] responses: '200': description: Array of position rows. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: type: array items: $ref: '#/components/schemas/Position' '401': $ref: '#/components/responses/Unauthorized' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/fills: get: operationId: listFills tags: - Market Data summary: List the authenticated account's recent fills. description: 'Returns matched-trade rows from the audit indexer. Each fill has a `your_side` (`maker` or `taker`) and a `your_action` (`buy` or `sell`) field framed from the caller''s perspective. Note: this endpoint queries the ClickHouse projection, which lags real-time by ~100 ms (the audit indexer''s flush interval). For latency-sensitive fill notifications, subscribe to the `fill` WebSocket channel instead. ' security: - BearerAuth: [] parameters: - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 1000 default: 100 description: Maximum rows to return (newest first). - name: cursor in: query required: false schema: type: string description: Opaque pagination cursor from a prior response's `next_cursor` (audit F3; same idiom as /v1/insurance_fund flows and the trades tape). responses: '200': description: One page of fills, newest first. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/FillsPage' '401': $ref: '#/components/responses/Unauthorized' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/margin_info: get: operationId: getMarginInfo tags: - Margin summary: Same-seq margin block projection (API-1) description: A read-only projection of the `margin` block alone; introduces zero engine state. security: - apiKey: [] responses: '200': description: Margin info content: application/json: schema: type: object required: - engine_seq_applied - margin properties: engine_seq_applied: type: integer format: int64 margin: $ref: '#/components/schemas/MarginBlock' /v1/insurance_fund: get: operationId: getInsuranceFund tags: - Market Data summary: Insurance-fund balance + itemized LQ-16 flows (API-4) description: '`balance_uusdc` is engine state (the venue INSURANCE account''s margin cash at `engine_seq_applied`). `flows` is the paginated itemized journal over exactly the six LQ-16 causes. Soak invariant: `balance == seeded + Σ(flows)`. Off-ledger recap claims are never a flow. ' parameters: - name: limit in: query schema: type: integer minimum: 1 maximum: 1000 default: 100 - name: cursor in: query schema: type: string responses: '200': description: Insurance fund content: application/json: schema: $ref: '#/components/schemas/InsuranceFund' /v1/liquidation_metrics: get: operationId: getLiquidationMetrics tags: - Market Data summary: T+24h delayed hourly liquidation aggregates (API-2 / §14.2.2) description: 'Per-instrument HOURLY `{liquidated_notional_uusdc, count}` aggregated from the API-3 fill tag, served on a T+24h delay — no bucket younger than 24h is ever returned (the real-time liquidation surface is deliberately deferred, §14.2.1). ' parameters: - name: instrument_id in: query schema: type: integer format: int64 responses: '200': description: Liquidation metrics content: application/json: schema: $ref: '#/components/schemas/LiquidationMetrics' /v1/contract_specs: get: operationId: listContractSpecs tags: - Market Data summary: List listed margined instruments with manifest hashes (F-6) responses: '200': description: Contract spec list content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: type: object required: - engine_seq_applied - instruments properties: engine_seq_applied: type: integer format: int64 instruments: type: array items: type: object required: - instrument_id - product_class - manifest_hash properties: instrument_id: type: integer format: int64 product_class: type: integer manifest_hash: type: string /v1/contract_specs/{instrument_id}: get: operationId: getContractSpec tags: - Market Data summary: Signed per-instrument manifest pinned by manifest_hash (F-6 / §19.3) description: '`manifest_hash` is the SHA-256 of the canonical `spec` bytes; a client re-marshals `spec` and re-hashes to verify byte-identity. Every snapshot/channel decode constant is resolvable from `spec`. A cryptographic signature over the hash is Phase-B (ORC-023). ' parameters: - name: instrument_id in: path required: true schema: type: integer format: int64 responses: '200': description: Contract spec content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/ContractSpecResponse' '404': $ref: '#/components/responses/NotFound' /v1/margin/transfer: post: operationId: marginTransfer tags: - Margin summary: Transfer cash between the spot balance and the margin sub-account. description: 'Moves settlement-asset cash between the account''s spot balance and its margin sub-account. `direction` is `in` (spot → margin) or `out` (margin → spot). Accepted for sequencing (202): the engine''s verdict is asynchronous and a rejected transfer applies nothing. Outbound transfers are bounded by the engine''s `margin_withdrawable` gate (`GET /v1/margin_info`). The authenticated account is the only account whose margin can move — a mismatched `account_id` in the body is rejected `forbidden`. ' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/MarginTransferRequest' examples: fund_margin: summary: Fund margin with $1,000 value: direction: in amount_uusdc: 1000000000 responses: '202': description: Accepted for sequencing. `status` is always `accepted`; the engine's apply/reject verdict is asynchronous. content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object required: - data properties: data: $ref: '#/components/schemas/MarginTransferResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': description: The body's `account_id` does not match the authenticated account, or the account's state forbids the action. '500': $ref: '#/components/responses/InternalError' /v1/ws: get: operationId: openWebSocket tags: - WebSocket summary: Open the WebSocket connection (HTTP/1.1 Upgrade). description: 'The Edge service speaks a small subscription-based protocol over WebSocket. The HTTP verb is `GET` with the standard `Upgrade: websocket` headers; after the upgrade, client and server exchange JSON frames. See `docs/exchange/api/services/websockets.md` for the protocol (subscribe/unsubscribe/ping operations) and the complete list of channels (`orderbook_delta`, `trade`, `candles`, `mark`, `market_lifecycle`, `fill`, `account`). Bearer authentication on the Upgrade request is optional but required for private channels (`fill`, `account`). Anonymous connections may subscribe to public channels only. This OpenAPI operation exists for codegen visibility; the wire-frame schemas are not modelled in OpenAPI itself (consider AsyncAPI for that). ' security: - {} - BearerAuth: [] parameters: - name: Upgrade in: header required: true schema: type: string enum: - websocket - name: Connection in: header required: true schema: type: string enum: - Upgrade - name: Sec-WebSocket-Key in: header required: true schema: type: string - name: Sec-WebSocket-Version in: header required: true schema: type: string enum: - '13' responses: '101': description: Protocol switched to WebSocket. From here, exchange JSON frames per the protocol in `docs/exchange/api/services/websockets.md`. x-websocket-channels: mark: scope: public gated_by: api.mark_channel_enabled description: API-2 public mark channel. Emits per mark update. payload: instrument_id: int64 mark: int64_ticks band_lo: int64_ticks band_hi: int64_ticks event_seq: int64 ts: int64 account: scope: private description: API-2 private account channel — carries the liquidation lifecycle and margin warnings for the authenticated account only. events: liquidation_started: canceled_order_count: int cured: bool event_seq: int64 liquidation_complete: filled_qty: int64 penalty: int64 shortfall_uusdc: int64 remainder_uusdc: int64 event_seq: int64 margin_warning: instrument: int64 equity_uusdc: int64 mm_requirement_uusdc: int64 mm_ratio_bps: int threshold_hit: int mark_event_seq: int64 settlement_applied: instrument_id: int64 vm_uusdc: int64 settle_ticks: int64 prev_settle_ticks: int64 event_seq: int64 fill: scope: private description: API-3 private fill channel. Carries the own-leg fee and the fill tag; liquidation fills are NOT on the public trade tape (§14.2.1). payload: fee_uusdc: int64 tag: normal|liquidation|adl