Professional Trading API
Contract 2.1 · DEV qualification candidate · 7 October 2026
Integrate a market-making application using OAuth, canonical REST or persistent WebSocket commands, a recoverable L2 book, and a private execution feed. These pages describe implemented contracts, not a production SLA. Environment activation and tested capacity must be confirmed during onboarding.
Publication on docs.ympredictions.com does not activate these interfaces in
PROD. The examples and downloadable contract target the qualified DEV release;
production endpoint availability requires a separate rollout and confirmation.
Start here
- Complete onboarding: one confidential client and one dedicated account, approved scopes, financial activation and UAT.
- Connect and place an order. Establish private-feed recovery and a fresh book before quoting.
- Implement cancellation safety, then reconcile executions and economics.
- Use the endpoint reference or downloadable OpenAPI for exact JSON shapes.
Three connections, three purposes
| Interface | Purpose | Recovery |
|---|---|---|
/v1/market-feed |
Shared, anonymous YES-coordinate L2 book | Replace books with fresh snapshots on every connection; no historical replay. |
/v1/order-entry or order REST |
Submit NEW, REPLACE and CANCEL | Persist outbound commands; retry an identical command with its original key. |
/v1/trading-feed |
Own command results, orders and financially posted fills | Signed account-bound cursor; at-least-once replay and versioned order snapshots. |
An acknowledgement means admission, not matching-engine completion. Do not infer your fills from public book changes. Identity comes from the validated bearer token, never a body field or client-selected account ID.
Core contract
- Orders name
action(BUY/SELL),outcome(YES/NO), integerprice_cents(0..100) in that outcome, and integer contract quantity. - Public books use YES prices. A NO command's price is not pre-complemented.
- Use immutable market IDs and their current positive
market_generation. - Read book currency from
/v1/account/trading-fees. DEV is a synthetic EUR book, not access to real-money trading. - Native post-only is maker-or-cancel, including on replacement. A crossing post-only replacement can cancel the existing quote; it does not promise to leave that quote working.
- Shared firm wallets, arbitrary subaccount selectors, FIX, atomic quote-set replacement and guaranteed cross-market order sequencing are not offered.
YM owns authentication, admission, collateral, financial posting, lifecycle and participant projections. DXmatch supplies matching and venue-order evidence. Participants use YM interfaces; direct DXmatch credentials are not required.
The handbook is generated from versioned guides and a pinned participant OpenAPI snapshot of the DEV release, and checked for drift. Report discrepancies with UTC time, environment, error code, request correlation ID and client request ID—never secrets or bearer tokens.
1. Quickstart
Environment: DEV qualification; approved synthetic UAT account only
This guide uses the v2 order contract. Confirm v2 activation for the target environment before sending commands; v1 fields are intentionally rejected.
This sequence opens the private feed before submitting an order, then follows
one order through NEW, REPLACE and CANCEL. Use an account and market
explicitly approved for UAT. Do not run the example against an unapproved
financial environment.
Prerequisites
YM supplies each approved integration with:
- an OAuth confidential
client_idandclient_secret; - the YM account ID bound to that client;
- the granted scopes;
- the permitted environment and test markets; and
- an operational contact.
The initial pilot binds one confidential client to one financial account. Do
not place a user_id in an order request.
Set local variables without committing their values:
export YM_BASE_URL="https://dev.ympredictions.com"
export YM_TOKEN_URL="https://auth.dev.ympredictions.com/realms/ymp/protocol/openid-connect/token"
export YM_CLIENT_ID="supplied-by-ym"
export YM_CLIENT_SECRET="supplied-by-ym"
export YM_ACCOUNT_ID="supplied-by-ym"
1. Obtain a short-lived access token
TOKEN_RESPONSE="$(curl -sS -X POST "$YM_TOKEN_URL" \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode "client_id=$YM_CLIENT_ID" \
--data-urlencode "client_secret=$YM_CLIENT_SECRET")"
export YM_ACCESS_TOKEN="$(printf '%s' "$TOKEN_RESPONSE" | jq -r '.access_token')"
The current pilot token lifetime is five minutes. Request a new token before expiry; do not expect a refresh token from the client-credentials grant.
2. Select a canonical market
curl -sS \
"$YM_BASE_URL/v1/markets?view=canonical&status=active&limit=20" \
| jq '.markets[] | {
market_id: .id,
market_generation,
status,
publication_state,
title
}'
Select a leaf with all of the following:
- a positive
market_generation; statusequal toOPEN;publication_stateequal toPUBLIC_OPEN; and- a close time that has not passed.
The order API remains the final admission authority. A catalog result is not a reservation of future tradeability.
export YM_MARKET_ID="selected-immutable-market-id"
export YM_MARKET_GENERATION="selected-positive-generation"
3. Establish a fresh L2 book
Connect to wss://dev.ympredictions.com/v1/market-feed?markets=<market-id>
with the same bearer header. Require a FRESH book.snapshot and validate every
connection sequence. Reconnect and replace the book on any gap. See
Market data for the complete consumer contract.
For a one-off diagnostic snapshot:
curl -sS \
"$YM_BASE_URL/v1/book?market_id=$YM_MARKET_ID&depth=20" \
| jq .
The snapshot is public. seq describes the current YM projection; it is not a
durable professional replay cursor. See Market data.
4. Open the private feed
Open this in a second terminal before sending the order:
npx wscat \
-c 'wss://dev.ympredictions.com/v1/trading-feed' \
-H "Authorization: Bearer $YM_ACCESS_TOKEN"
Wait for this bootstrap sequence:
stream.welcome
stream.snapshot_started
stream.snapshot_chunk (zero or more)
stream.snapshot_completed
stream.ready
Only submit commands after stream.ready. Persist each event's signed cursor
after the event has been validated and durably applied locally.
5. Submit a resting order
The example buys ten YES contracts at 45 cents using GTC:
export YM_CLIENT_ORDER_ID="quickstart-order-$(date +%s)"
export YM_NEW_REQUEST_ID="quickstart-new-$(date +%s)-$RANDOM"
NEW_RESPONSE="$(curl -sS -X POST "$YM_BASE_URL/v1/orders" \
-H "Authorization: Bearer $YM_ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $YM_NEW_REQUEST_ID" \
--data "{
\"client_order_id\": \"$YM_CLIENT_ORDER_ID\",
\"market_id\": \"$YM_MARKET_ID\",
\"market_generation\": $YM_MARKET_GENERATION,
\"action\": \"BUY\",
\"outcome\": \"YES\",
\"price_cents\": 45,
\"quantity\": 10,
\"time_in_force\": \"GTC\"
}")"
printf '%s' "$NEW_RESPONSE" | jq .
export YM_ORDER_ID="$(printf '%s' "$NEW_RESPONSE" | jq -r '.order.id')"
201 Created means YM admitted the command and durably created a PENDING
order. Wait for order.working on the private feed, or read the canonical order
until execution_status is WORKING, before treating it as venue-effective.
For a NO order, keep the same coordinate end to end. A request with
"action":"BUY", "outcome":"NO", and "price_cents":35 must return and
stream BUY / NO / 35. Never send or expect the internal complementary
YES-book representation.
6. Read the canonical order
curl -sS \
"$YM_BASE_URL/v1/orders/$YM_ORDER_ID" \
-H "Authorization: Bearer $YM_ACCESS_TOKEN" \
| jq .
Record order_version. A replacement is fenced by the version it was based on.
7. Replace the working order
This changes the effective price of the order's immutable YES outcome to 44 cents while retaining the target total quantity of ten contracts:
ORDER_RESPONSE="$(curl -sS \
"$YM_BASE_URL/v1/orders/$YM_ORDER_ID" \
-H "Authorization: Bearer $YM_ACCESS_TOKEN")"
export YM_ORDER_VERSION="$(printf '%s' "$ORDER_RESPONSE" | jq -r '.order_version')"
export YM_REPLACE_REQUEST_ID="quickstart-replace-$(date +%s)-$RANDOM"
curl -sS -X POST \
"$YM_BASE_URL/v1/orders/$YM_ORDER_ID/replace" \
-H "Authorization: Bearer $YM_ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $YM_REPLACE_REQUEST_ID" \
--data "{
\"expected_order_version\": $YM_ORDER_VERSION,
\"price_cents\": 44,
\"total_quantity\": 10
}" | jq .
202 Accepted means the replacement command was admitted. The prior
venue-effective price and quantity remain authoritative until
order.replaced arrives.
8. Cancel the order
Do not send a cancel while a replacement is still pending. Wait for
order.replaced, then use a new idempotency key:
export YM_CANCEL_REQUEST_ID="quickstart-cancel-$(date +%s)-$RANDOM"
curl -sS -X POST \
"$YM_BASE_URL/v1/orders/$YM_ORDER_ID/cancel" \
-H "Authorization: Bearer $YM_ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $YM_CANCEL_REQUEST_ID" \
--data '{}' | jq .
202 Accepted is not cancellation finality. Wait for order.cancelled or a
canonical terminal snapshot before releasing local risk.
9. Resume after disconnect
Reconnect with the last cursor your application fully applied:
wss://dev.ympredictions.com/v1/trading-feed?cursor=<URL-encoded-signed-cursor>
The server replays events after that cursor and sends stream.ready. Delivery
is at least once; deduplicate using event_id and never derive sequence numbers
from the cursor.
Forward path
REST is the pilot command transport. Persistent WebSocket order entry is in development and will use the same IDs, idempotency, market-generation fence and canonical lifecycle. Existing REST clients will not need a separate business model when that transport becomes available.
2. API reference and versioning
Download OpenAPI 3.1. This publication pins a DEV-release contract extracted from the platform specification with a participant endpoint allowlist and all referenced schemas. Internal services, operator administration, payments, retired order fields and retail-only streaming interfaces are excluded.
The public namespace remains /v1; schema_version: 2 identifies canonical
order and private-feed payloads. The book stream uses ym.market-data.v1, and
the command socket uses ym.order-entry.v1. These are separate contracts.
REST documentation describes WebSocket upgrades; the corresponding guide gives message ordering, recovery and client obligations. An HTTP schema alone is not an execution-state machine.
Compatibility
Ignore unknown optional response fields. Do not send unknown request fields or
enums. Changes to price coordinates, identity, recovery or existing field
meanings require an explicit migration. Old side, position_side, intent
and decimal price order inputs are not v2 aliases and are rejected.
V1 private-feed cursors cannot resume v2. Retain historical terminal/fill data through any announced migration; a cursorless snapshot replaces only current nonterminal orders, not account history.
Maintaining this publication
From Backend/scripts/market-maker:
npm ci --ignore-scripts
npm run generate
npm run check
npm test
On the docs-only main branch, generation reads the published participant YAML,
not main's application specification. Source revision and contract hashes are
recorded in publication-source.json alongside the generator. Update this
snapshot from the reviewed DEV release when its contract changes; the source
hash test intentionally fails until that update is recorded explicitly.
The HTML handbook and endpoint reference are generated artifacts. The participant YAML remains the pinned, self-contained source for this publication. The older pilot HTML redirects here; its old PDF is not the current contract. Documentation does not itself activate a feature on another environment. Consult the environment's deployed revision and UAT sign-off.
3. Authentication and scopes
Use OAuth 2.0 client credentials. YM supplies a confidential client bound to a single dedicated financial account. Keep the secret in your secret manager; never send it to the trading API or place credentials in URLs.
POST https://auth.dev.ympredictions.com/realms/ymp/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id=<assigned>&client_secret=<secret>
URL-encode all form values. Authenticate REST and all three headless WebSocket
upgrades with Authorization: Bearer <access_token>. Do not request browser
password grants, invent a user_id, or reuse another firm's credentials.
| Scope | Authority |
|---|---|
orders:write |
Canonical order commands and own cancellation controls. |
orders:read |
Own orders, positions, executions, fee policy and L2 stream. |
trading-feed:read |
Own resumable private order/execution stream. |
balances:read |
Own balances, ledger evidence and allocated rebates. |
trading:professional |
Configured professional rate policy; grants no trading or data authority alone. |
Scopes are operator-assigned, not elevated by requesting more scope at login. Tokens identify the service-account subject. This exact subject must be the approved YM financial account. Client names and display names are not identity.
Token renewal
Use expires_in; the current configured lifetime is 300 seconds. Obtain a new
token before expiry, with jitter. Client credentials do not require or promise
a refresh token. Cache secrets only in memory or an approved secret store.
Reconnect the private feed from its last durably applied cursor, rebuild public books from fresh snapshots, and reconnect command entry with the new token. Do not retransmit uncertain commands with new keys. Token expiry ends the session; per-command authorization still applies on the command socket.
Rotation prevents obtaining new tokens with the old secret; it does not alone invalidate every already-issued token. Emergency trading containment also requires an account restriction, terminal cancellation and verification. See Onboarding.
Isolation and transport
Balance path IDs must equal the token subject. Private cursors are signed and account-bound. Execution and ledger reads never return an opposing participant's identity or postings. Public books deliberately aggregate anonymous price levels from all participants.
Headless clients may omit Origin. Browser protections remain active. Use TLS
and never disable certificate validation. A 401 requires authentication recovery;
a 403 requires correcting entitlement or account state, not a retry storm.
Honor Retry-After for 429 responses.
4. Markets, identity and lifecycle
Canonical discovery is REST; subscribed book streams carry generation, freshness and open/closed status. Catalog details still come from REST.
Every trading command targets one immutable leaf market_id and the exact
current market_generation. Titles, slugs, URLs and provider identifiers are
presentation or discovery data and must not be used as trading identity.
Hierarchy
| Resource | Purpose |
|---|---|
| Event | The real-world occurrence, fixture, election or question grouping. |
| Market family | A coordinated proposition such as match winner, set winner or tournament winner. |
| Market leaf | One binary outcome market. Public orders select YES or NO; YM normalizes both to the leaf's internal YES book. |
A family may contain one or many leaves. membership_complete describes
whether the canonical family membership is complete; it does not make the
entire family one tradable instrument.
Canonical discovery
Always request the canonical view:
GET /v1/markets?view=canonical
GET /v1/markets/{marketID}?view=canonical
GET /v1/events
GET /v1/events/{eventID}
GET /v1/events/{eventID}/market-families
GET /v1/market-families?view=canonical
GET /v1/market-families/{familyID}?view=canonical
Canonical collection pagination uses the opaque next_cursor; offset
pagination is unsupported. Treat cursors as opaque and scoped to the request
shape that produced them.
Provider mappings are returned only through explicit canonical expansions. They are diagnostic metadata, not stable order-entry identifiers.
Trading eligibility indicators
Before submitting an order, require:
market_generation > 0;- market
statusequal toOPEN; publication_stateequal toPUBLIC_OPEN; andclose_at, when present, later than the current UTC time.
These are client-side preflight indicators. YM rechecks authoritative admission, lifecycle, participant and collateral state atomically during order entry. A market can stop accepting commands after it was discovered.
PUBLIC_BLOCKED may remain visible for discovery, live presentation or
history while admission is disabled. It is not a tradable state.
Market generation
market_generation is an optimistic safety fence around the market's current
admission definition. It prevents a command constructed against an older
definition from entering a newer market state.
On 409 stale_market_generation:
- Stop sending commands with the old generation.
- Fetch
GET /v1/markets/{marketID}?view=canonical. - Re-evaluate status, publication, close time and the changed definition.
- Rebuild the command with the new positive generation.
- Use a new client request ID if the economic command changed. Reuse the old ID only to recover the exact original payload.
Never increment or infer a generation locally.
Lifecycle behavior
- An
OPENmarket may accept orders when its admission and publication state also allow trading. - Closing begins a controlled stop-and-drain process. A displayed live status alone does not override admission state.
CLOSEDprevents new exposure while finality or settlement may still be in progress.RESOLVEDrecords the canonical result. Historical discovery may retain the market after trading has ended.- Existing nonterminal orders may receive system-generated cancellation or expiry events as part of lifecycle control. These appear on the same private feed as client commands.
Do not assume the scheduled start of an event is necessarily its trading close or resolution time. Use the canonical market fields and effective lifecycle events.
Revision versus generation
revision tracks canonical presentation/catalog changes. market_generation
is the trading admission fence. A client must not substitute one for the
other.
Streaming status
The professional book stream includes the catalog generation, lifecycle and fresh/stale status for subscribed markets. Stop quoting when it becomes stale or closed; fetch canonical details when generation changes. It does not replace catalog pagination or promise an ordered history of every catalog revision. See Market data.
5. Order entry and lifecycle
Status: canonical REST NEW, REPLACE and CANCEL available in the
controlled pilot
YM exposes one canonical order model. REST admission, canonical reads and the private feed describe the same order and command identities. The persistent WebSocket command adapter enters this same business path; it does not create separate order semantics. FIX is not offered.
Three distinct states
Never collapse these layers into one generic accepted state:
| Layer | Authority | Meaning |
|---|---|---|
| Command admission | YM | The command was durably accepted or rejected by YM. |
| Exchange execution | DXmatch evidence projected by YM | The effective working, filled, replaced or terminal state. |
| Financial posting | YM ledger | The resulting hold, fill and settlement posting state. |
An HTTP 201 or 202 is command admission, not venue finality.
Action, outcome and price semantics
The public contract always describes the participant's selected outcome.
Clients send action, outcome, and that outcome's price_cents:
action |
outcome |
Meaning |
|---|---|---|
BUY |
YES |
Increase YES exposure at the stated YES price. |
SELL |
YES |
Reduce YES exposure at the stated YES price. |
BUY |
NO |
Increase NO exposure at the stated NO price. |
SELL |
NO |
Reduce NO exposure at the stated NO price. |
YM derives the internal YES-book side and OPEN/CLOSE accounting effect.
Do not complement NO prices: BUY NO at 35 means pay 35 cents for NO. Naked
shorting is not exposed; a SELL must be covered by the named outcome position.
Prices are integer cents from 0 through 100; quantities are positive whole
contracts. The public professional contract currently exposes limit orders
only.
Time in force
| Value | Behavior |
|---|---|
GTC |
Rest until filled, explicitly cancelled or removed by market lifecycle. Preferred for professional resting liquidity. |
IOC |
Attempt immediate execution and cancel any unfilled remainder. It remains a limit order with price protection. |
DAY |
Venue-supported session-scoped lifetime. Do not use it as a substitute for a long-lived prediction-market order. |
GTD, FOK, market, stop and iceberg orders are not currently exposed.
NEW
POST /v1/orders
Authorization: Bearer <token>
Idempotency-Key: <participant-scoped-command-id>
Content-Type: application/json
Required body:
{
"client_order_id": "strategy-a-000001",
"market_id": "immutable-market-id",
"market_generation": 7,
"action": "BUY",
"outcome": "YES",
"price_cents": 45,
"quantity": 100,
"time_in_force": "GTC"
}
Do not send user_id. client_order_id is participant-defined, has a maximum
length of 200 characters and must remain unique within the participant's
retained order history.
Do not send the retired v1 fields side, position_side, intent, or decimal
price. Unknown request fields fail closed.
Responses:
201: newly admitted command with aPENDINGorder.200: exact idempotent replay of the original command.4xx/5xx: inspect the canonical error and follow the retry policy.
YM transactionally records the admitted command, order, collateral reservation and durable downstream work before responding. DXmatch execution remains asynchronous.
REPLACE
POST /v1/orders/{orderID}/replace
Idempotency-Key: <new-command-id>
{
"expected_order_version": 4,
"price_cents": 46,
"total_quantity": 120
}
Rules:
- Use the
order_versionfrom the latest canonical order snapshot. price_centsremains in the order's immutableoutcomecoordinate. A NO replacement is therefore also a NO price and must not be complemented.total_quantityis the target total for the order, including quantity already filled. It must exceedcumulative_filled_quantity.- A replacement receives its own idempotency key.
- Only one mutation may be pending on an order.
202means the replacement was admitted. Untilorder.replaced, the prior effective price and quantity remain authoritative.- A rejected replacement leaves the prior effective order in place.
- Fills racing with an admitted replacement remain valid and are reflected in subsequent order versions.
DX applies an accepted REPLACE to one existing venue order as a single-order mutation. YM admission is not proof that it became effective. REPLACE does not atomically replace a bid/ask pair.
CANCEL
POST /v1/orders/{orderID}/cancel
Idempotency-Key: <new-command-id>
Content-Type: application/json
{}
202means a venue-directed cancel was admitted andpending_action=CANCEL.200means cancellation is already locally effective or the command is an idempotent completed/no-op result.- Cancellation is final only when the canonical order is terminal or an
order.cancelledevent is applied. - Lifecycle and risk cancellations use the same canonical order state and are visible even when initiated outside the current client connection.
Idempotency
All single-order mutations require Idempotency-Key:
- maximum 200 characters;
- scoped to the authenticated participant;
- the
ym:prefix is reserved; - retry an uncertain request with the exact same key and payload;
- reusing a key with a different payload is a conflict; and
- use a distinct key for each distinct economic command.
Transport timeout is not evidence of failure. Recover by replaying the exact request or querying the resulting canonical order.
Clients must durably retain each outbound command payload under its
client_request_id. A command result identifies admission/outcome and current
order state; it is not a copy of the client's command-request journal. There is
currently no general query-by-client-request-ID endpoint.
Batch commands
POST /v1/orders/batch
POST /v1/orders/cancel-batch
The contract accepts at most 250 items. Each item carries its own
client_request_id and returns its own status, acceptance and retryability.
A batch is a transport convenience, not one atomic multi-order transaction. Clients must inspect every item and retry only the exact uncertain items with the same item IDs and payloads.
Canonical reads
GET /v1/orders/{orderID}
GET /v1/orders?market_id=...&execution_status=...&cursor=...&limit=...
Order reads expose:
- immutable public
actionandoutcome, with all prices and filled notional expressed in that outcome's coordinate; execution_status:PENDING,WORKING,PARTIALLY_FILLED,FILLED,CANCELLED,EXPIREDorREJECTED;pending_action:REPLACE,CANCELor null;state_quality:CONFIRMEDorRECONCILIATION_REQUIRED;- original, current total, cumulative filled, remaining and working quantity;
order_version; and- admitted, venue-accepted, fill, terminal and update timestamps when known.
The single-order response supplies an ETag derived from order_version and
accepts If-None-Match.
If state_quality=RECONCILIATION_REQUIRED, stop mutating that order until YM
has restored confirmed venue truth.
Canonical REST command and list envelopes carry schema_version: 2. The
single-order GET returns the bare v2 Order resource; its URL remains /v1
and it does not add a second schema envelope.
Account projections
GET /v1/executions?from=...&to=...
GET /v1/positions
GET /v1/portfolio
GET /v1/accounts/{selfAccountID}/balances
These routes are self-scoped by the bearer token. Private fill events currently
report financial settlement_status=POSTED; a fill is not presented as
financially complete before its approved posting path succeeds.
GET /v1/trades is a legacy account-activity projection outside Order
Contract v2: side and price_cents are
in the YES-book venue coordinate, not the selected-outcome v2 order coordinate.
It is useful for reconciliation but is not the live v2 recovery authority. Use
private-feed order.fill, whose nested order and fill price are participant-
relative, for live strategy state. The legacy endpoint is deliberately omitted
from the curated v2 OpenAPI.
Native post-only
Set post_only: true on NEW with GTC or DAY; IOC with post-only is rejected
before admission. The flag is immutable and retained by REPLACE. DXmatch
enforces native maker-or-cancel: a crossing NEW is cancelled without a fill.
A crossing post-only REPLACE can cancel the existing quote. This is an
effective terminal outcome, not a rejected replacement that leaves the old
quote working. Observe private events and the canonical order.
Persistent command entry
Connect to wss://dev.ympredictions.com/v1/order-entry with orders:write.
Validate welcome contract ym.order-entry.v1. A frame wraps the identical
canonical REST command:
{"request_id":"connection-correlation-17","operation":"REPLACE","client_request_id":"durable-replace-key","order_id":"order-uuid","body":{"expected_order_version":4,"price_cents":44,"total_quantity":10}}
operation is NEW, REPLACE, CANCEL or CANCEL_ALL. NEW/CANCEL_ALL omit
order_id; CANCEL has no body or {}. NEW/REPLACE bodies are the REST JSON
objects; CANCEL_ALL is the optional market scope. Batch commands remain REST.
Responses have type: "command.response", your request_id, HTTP-equivalent
status, the canonical response body, and optional retry_after. Concurrent
responses may arrive out of order. Never reuse a correlation ID while it is in
flight; the durable client_request_id is the idempotency key across REST and
WebSocket. A socket close or 503 leaves outcome uncertain: retry the identical
payload/key, not a newly generated command.
There are 12 normal and 4 reserved cancellation slots per connection. An
inflight_limit 429 is explicitly not forwarded. Do not treat every other
transport error as proof of non-execution. Keep the private feed open for
effective outcomes; no execution events are multiplexed onto this command
socket. See Cancellation safety for shutdown controls.
6. Private trading feed
Status: controlled pilot
Current message schema: v2
Endpoint:
wss://dev.ympredictions.com/v1/trading-feed
Required scope: trading-feed:read
The feed is participant-scoped and read-only. It reports canonical command, order, replacement, fill and terminal state regardless of whether the change originated from REST, lifecycle control or another authorized session for the same account.
Connection
Pass the access token in the WebSocket upgrade request:
Authorization: Bearer <access_token>
Participant identity is derived from the token. No user_id, account, MPID or
venue-session selector is accepted.
WebSocket compression is disabled for the low-latency pilot profile. Clients
must accept JSON text frames up to the max_frame_bytes announced in
stream.welcome.
New session bootstrap
Without a cursor, the feed sends:
stream.welcomestream.snapshot_started- zero or more
stream.snapshot_chunkframes stream.snapshot_completed- events committed after the snapshot high-water mark
stream.ready- live lifecycle events and
stream.heartbeat
The snapshot contains all canonical nonterminal orders for the authenticated
participant. Replace local nonterminal state with the completed snapshot, then
apply replayed events in order. Do not begin strategy command flow before
stream.ready.
stream.welcome advertises the contract name, heartbeat interval, maximum
frame size and at_least_once delivery mode. Use those values rather than
hardcoding deployment defaults.
For v2 the advertised contract is Private Order & Execution Feed v2, and
every lifecycle and control message carries schema_version: 2.
Resume
Each lifecycle event includes an opaque signed cursor. Persist it only after
the event has been validated and durably applied locally.
Reconnect with:
wss://dev.ympredictions.com/v1/trading-feed?cursor=<URL-encoded-last-applied-cursor>
The server replays events after the supplied cursor, then sends
stream.ready. A resume session does not send another account snapshot unless
the client reconnects without a cursor.
The cursor is:
- opaque;
- signed by YM;
- bound to the authenticated participant and stream; and
- not a DXmatch offset or a number clients may increment.
A cursor issued by v1 is invalid for v2. Before cutover, durably consume v1 through the announced boundary and retain terminal/fill history. Then discard the v1 cursor and connect without a cursor. The v2 snapshot replaces only nonterminal order state; it does not replay v1 terminal orders or fill history.
Delivery and convergence
Delivery is at least once. A correct consumer must:
- Require
schema_version == 2and the expected account scope. - Deduplicate lifecycle facts by
event_id. - Apply an order snapshot only when its
order_versionis newer than the locally stored version. - Persist the event and its cursor atomically in the client's own store.
- Keep independent orders independent; do not assume one global exchange ordering across markets.
An admitted mutation may produce an admission PENDING command result and a
later terminal command result. These are different facts with different
event_id values but the same command.command_id; do not deduplicate them by
command identity. Repeated delivery of either fact retains its event identity.
Rejected-at-admission commands have only their admission result.
Apply a command result independently of the embedded order snapshot. A new
terminal command outcome still matters when that snapshot's order_version
is equal to or older than the locally stored order version. Once a command is
terminal, a delayed admission PENDING/UNKNOWN fact must not regress it.
Publication and replay may deliver these facts in a different order.
Existing retained event IDs and signed cursors remain valid; this distinction does not rewrite historical journal payloads or manufacture missing historical terminal results.
Duplicate delivery is normal recovery behavior and must not create a duplicate fill or command.
The feed is a result/state journal, not the participant's outbound request
journal. Persist every submitted payload locally under client_request_id;
pending replacements intentionally retain the prior effective order terms
until DX confirms the mutation.
Lifecycle events
| Event | Meaning |
|---|---|
order.admitted |
A canonical order was admitted by YM; it is not necessarily working at DXmatch. |
order.working |
Venue evidence confirms the order is working. |
order.replaced |
Venue evidence confirms the new single-order price/quantity is effective. |
order.fill |
A fill and complete updated order snapshot are available. |
order.cancelled |
Cancellation is effective. |
order.expired |
The venue/lifecycle made the order terminal by expiry. |
order.rejected |
The order was rejected and is terminal. |
order.command_result |
Outcome for REPLACE or CANCEL, and for a rejected NEW; a rejected NEW may have no order. Successful NEW admission is order.admitted. |
Every ordinary lifecycle event carries a complete canonical order snapshot.
The sole orderless case is a schema-valid NEW rejected before an order could
exist.
Relevant event fields include:
event_id,event_type,cursorandorder_version;- complete
orderstate; - participant-relative
action,outcome, outcome price, and filled notional; - originating command and client-request identity where applicable;
- participant-safe venue evidence: symbol, the participant's execution ID, shared trade/match ID when supplied, and venue event time;
- fill price, quantity, maker/taker role and posting state; and
- YM receive, projection and socket-send timestamps.
order.price_cents, order.filled_notional_cents, and fill.price_cents are
all expressed in order.outcome. Clients never complement NO values.
Raw DXmatch replay offsets, MPID/account/session topology and the opposite participant's execution identifier are deliberately not exposed.
Control errors
The server may send stream.error and close the connection:
| Code | Action |
|---|---|
INVALID_CURSOR |
Discard the cursor only after confirming the connection used the correct participant; reconnect without it for a fresh snapshot. |
CURSOR_EXPIRED |
Reconnect without the cursor and rebuild nonterminal state from a fresh snapshot. |
REPLAY_LIMIT_EXCEEDED |
Reconnect without the cursor; investigate why the client fell behind. |
SLOW_CONSUMER |
Stop command flow, improve local consumption, then resume from last_written_cursor or the client's earlier safely applied cursor. |
STREAM_ERROR |
Preserve the last safely applied cursor and reconnect with bounded backoff. Escalate persistent failures. |
last_written_cursor means the last event written to the socket, not
necessarily the last event your application applied. Prefer your own durable
last-applied cursor.
Heartbeats and token rotation
Treat absence of both data and heartbeats beyond the announced interval plus a small network allowance as a disconnected session. Refresh the short-lived OAuth token out of band, open a replacement connection and resume from the last applied cursor.
Client-to-server application messages are not part of Private Feed v2. Standard WebSocket ping/pong may be used by the transport, but it does not acknowledge business events.
Forward path
The pilot feed already provides participant isolation, snapshot, replay and resume. Before contractual general availability, YM intends to complete multi-instance/high-availability evidence, retention and disaster-recovery proof, sustained slow-consumer testing and a published availability target. Future command WebSockets will share this canonical event stream rather than replace it.
7. Errors and retry policy
Status: trading-core canonical error envelope available; gateway-edge envelope and public versioned error registry in development
Canonical order errors use this shape:
{
"code": "stale_market_generation",
"message": "market generation is stale",
"retryable": false,
"command_id": null,
"client_request_id": "strategy-a-new-42",
"details": {}
}
The deprecated error field may duplicate message during transition. New
clients should use code, message and retryable. Authentication,
authorization, gateway rate-limit, and upstream-availability failures may
still arrive as the transport-edge shape {"error":"..."}. Treat those as
HTTP transport failures; do not infer a command outcome from that body.
HTTP policy
| Status | General meaning | Client action |
|---|---|---|
200 |
Read success, exact idempotent replay, or locally completed/no-op mutation. | Inspect the canonical command and order. |
201 |
A new order command was durably admitted. | Wait for exchange-effective state. |
202 |
A replace or venue-directed cancel was durably admitted. | Wait for the corresponding effective event. |
304 |
The order version matches If-None-Match. |
Keep the local snapshot. |
400 |
Invalid request or unsupported contract value. | Correct the request; do not blind-retry. |
401 |
Missing, invalid or expired token. | Obtain a valid token, then retry with the same command ID and payload. |
403 |
Scope, account or policy denial. | Do not retry until authorization/policy changes. |
404 |
Resource not found in the participant scope. | Reconcile identity; do not create a replacement identity blindly. |
409 |
Current state conflicts with the command. | Follow the specific code below. |
429 |
Rate limit exceeded. | Honor Retry-After; preserve command identity. |
503 |
Required admission, reconciliation or rate-limit dependency unavailable. | Follow retryable; use the same ID for an uncertain command. |
Important validation codes
These require a corrected request rather than a retry:
missing_idempotency_keyinvalid_idempotency_keyreserved_idempotency_keyinvalid_jsoninvalid_canonical_orderinvalid_orderinvalid_order_idinvalid_replace
Identity and idempotency conflicts
| Code | Meaning | Action |
|---|---|---|
idempotency_payload_conflict |
The NEW key already identifies a different payload. | Generate a new key only for a genuinely new command. |
replace_command_conflict / cancel_command_conflict |
The mutation key conflicts with an existing command payload. | Reconcile the original command; do not overwrite its identity. |
client_order_id_conflict |
The participant has retained that client order ID already. | Reuse it only to recover the original order; otherwise choose a new order ID. |
command_already_pending |
Another mutation is awaiting effective venue state. | Wait for the private-feed outcome, refresh the order, then decide again. |
Market and quote conflicts
| Code | Meaning | Action |
|---|---|---|
stale_market_generation |
The command used an old admission generation. | Refresh the canonical market and re-evaluate the command. |
market_not_open |
Lifecycle no longer accepts this command. | Stop sending until a later canonical state explicitly permits it. |
market_temporarily_unavailable |
Admission/readiness is currently blocked. | Refresh state; retry only if the same command remains valid. |
market_quote_unavailable |
The protected IOC quote could not be verified and the command was terminally rejected. | Refresh the book and make a new economic decision under a new ID; replaying the old ID only recovers that rejection. |
market_quote_no_liquidity |
No executable protected liquidity exists. | Wait for book change or submit a different limit command with a new ID. |
market_quote_moved |
The protected quote changed before admission. | Refresh and consciously reprice using a new command identity. |
market_quote_slippage_exceeded |
Available execution exceeded the supplied protection. | Re-evaluate price protection; never auto-widen without strategy authority. |
Replace conflicts
| Code | Meaning | Action |
|---|---|---|
order_not_replaceable |
The order is no longer in a replaceable working state. | Refresh the order. |
order_version_conflict |
expected_order_version is stale. |
Refresh and decide against the latest version. |
replace_quantity_filled |
Target total does not exceed cumulative fills. | Refresh fill state and choose a valid target. |
replace_noop |
Target price and remaining quantity are already effective. | Treat as no change; do not loop. |
insufficient_replace_reserve |
Additional collateral/exposure is unavailable. | Reduce/cancel or fund the account; do not blind-retry. |
short_quantity_increase_not_supported |
This BUY NO order's total quantity cannot currently be increased safely. | Cancel it and submit a separately collateralized NEW if still intended. |
Coverage and participant policy
insufficient_coveragemeans available cash or position coverage is insufficient at admission.money_gate_closed,user_not_eligibleandresponsible_gambling_*are policy decisions, not transient venue failures.order_admission_busymay be retried with bounded jitter and the same exact ID/payload.
Uncertain outcomes
command_commit_unknown is the critical recovery case: YM could not prove to
the caller whether the command transaction committed. Retry the exact command
with the same Idempotency-Key. Do not generate a new key.
reconciliation_required means YM will not return or mutate uncertain
canonical state. Stop mutation, preserve the last confirmed state and retry the
read with bounded backoff. Escalate if it persists.
For connection loss or any HTTP timeout after request transmission:
- Do not infer failure.
- Retry the exact payload with the same key.
- Query orders and consume the private feed.
- Create a new command ID only after the original outcome is known and a new economic decision is made.
If a response or feed event contains a canonical command with
admission_status=REJECTED, that identity is terminal even when its transport
status was 503. Replaying it recovers the same rejection; it does not rerun
admission.
Backoff
Use bounded exponential backoff with jitter for retryable transport and 5xx
failures. Honor Retry-After on 429. Cancellation and risk-reducing traffic
should use the separately provisioned cancellation capacity but must not spin
without backpressure.
Forward path
YM intends to publish a generated, versioned registry containing every external error code, retry class and remediation, then enforce it against handler tests. Until that is complete, the curated OpenAPI plus this policy define the pilot contract; unknown codes must fail safe and be reported with correlation data.
8. Recoverable market data
wss://dev.ympredictions.com/v1/market-feed?markets=<URL-encoded-comma-separated-IDs>
Authorization: Bearer <token with orders:read>
Subscribe to 1–100 distinct, published immutable market IDs. Wildcards, cursors and dynamic subscription messages are not supported. Change subscriptions by opening a new connection. An unknown or unpublished market rejects the whole handshake instead of silently omitting it.
Wire contract
Every application message has this envelope:
{"stream_id":"connection-uuid","sequence":1,"event":{"type":"stream.welcome","data":{"contract":"ym.market-data.v1","heartbeat_ms":5000,"recovery":"new_connection_snapshot","price_coordinate":"YES","markets":["market-id"]}}}
sequence starts at 1 and increases by one across all messages on that
connection. It is not a market, database or DXmatch replay offset. Validate it
before applying an event. Gaps, an unexpected stream ID, stale data or a broken
connection invalidate the local book.
| Event | Client action |
|---|---|
stream.welcome |
Validate contract, subscription and heartbeat. |
book.snapshot |
Atomically replace both sides; establish generation and feed epoch. |
book.delta |
Set side/price to absolute quantity; zero deletes the level. |
book.changes |
Apply all level edits atomically, never a half-applied book image. |
market.status |
Stop quoting on STALE or CLOSED. |
stream.ready |
Initial messages queued; does not mean every market is fresh. |
stream.heartbeat |
Connection liveness, not market freshness. |
Snapshot data contains market_id, market_generation, lifecycle, state,
feed_epoch, bids, asks and updated_at. Levels contain price_cents,
decimal compatibility price, and integer qty. Delta data contains
market_id, market_generation, side (BUY for bids / SELL for asks), price_cents, absolute
quantity and observed_at. book.changes has the same market/generation/time
with a changes array of level edits.
Source cadence and timestamp meaning
The shared L2 book, REST snapshots and derived best prices use coherent DX
marketDepth snapshots only. Top-of-book quotes are not depth updates. Apply
every book.changes message atomically, including removal of replaced levels.
DX may combine intermediate book states into its latest snapshot. Consequently, a continuous YM connection sequence does not prove that every exchange book change was delivered. Use the private trading feed for your own effective orders and fills; do not wait for a public depth update to acknowledge them. The public book is not a lossless order-flow or queue-priority feed.
observed_at is YM's processing timestamp, not the time DX changed its book.
Receipt minus observed_at measures the downstream delivery leg only. Heartbeat
frequency is independent of depth-update frequency. There is currently no
contractual depth refresh rate or exchange-to-client latency guarantee; clients
cannot request a faster upstream rate through /v1/market-feed.
Only a FRESH snapshot establishes a usable book, derived from the current DX
feed epoch and catalog generation while the market is open. Stale reasons can
be awaiting_snapshot, upstream_unavailable or market_unavailable; a closed
market reports not_open. Recovery supplies a complete snapshot before deltas
resume. Observe the catalog's close_at yourself; do not wait for a heartbeat
to stop quoting after a known deadline.
Reconnect and slow consumers
There is no historical book replay. On every connection rebuild books from new snapshots. Do not combine an old REST snapshot with a new connection's deltas. Reconnecting to another server follows the same rule.
Each connection is bounded at 256 queued messages and 2 MiB. Overflow disconnects instead of silently dropping updates. Handle WebSocket pings promptly; use the advertised heartbeat and your own staleness timer. Write timeout is 2 seconds; pong/read timeout is 15 seconds. Do not send application subscription frames.
Prices and supplementary REST
Public L2 and tape use YES-book prices. A YES ask at 65 cents is liquidity
against BUY YES @65 or SELL NO @35. A YES bid at 65 is liquidity against
SELL YES @65 or BUY NO @35. Commands send selected-outcome prices unchanged.
GET /v1/book?market_id=...&depth=20 and /v1/markets/{id}/book provide
snapshots (depth at most 200). Their projection seq is not this stream's
sequence. /v1/markets/{id}/trades?limit=200 returns recent public trades;
/stats returns analytics. Streaming public tape is not offered by this
contract; private fills come from the private feed, not L2 inference.
Anchors are reference signals, not executable prices. Retail /v1/ws is not
the professional recovery contract. This feed fans out existing in-memory
venue-derived state with bounded, nonblocking queues, without per-update
database reads or journal writes.
9. Limits and operating envelope
DEV qualification is not a production SLA or unlimited-capacity promise. Obtain the environment's approved rate, market scope and contact before starting a strategy. Nothing here authorizes production trading or bypasses collateral, KYC, risk or account controls.
| Resource | Protocol limit |
|---|---|
| Idempotency key / client order ID | 200 characters; ym: request prefix reserved |
| Batch NEW / CANCEL | 250 items; independent outcomes |
| Orders / canonical market page | 500 items |
| Execution page / time window | 500 fills / 31 days |
| Ledger read | 100 exact entry IDs; 10,000 returned postings |
| REST book depth / public trades | 200 per side / 200 trades |
| Book subscriptions | 100 markets per connection |
| Book connections | 4 per account, 64 per market-data process |
| Command connections | 4 per account, 64 per gateway process |
| Command socket in flight | 12 ordinary plus 4 reserved cancellation slots per connection |
| Command frame / response | 16 KiB / 64 KiB |
| Book outgoing queue | 256 messages and 2 MiB; disconnect on overflow |
| Manual cancel cohort | 10,000 orders; split larger scopes by market |
| Dead-man timeout | 10–120 seconds; large expiry cohorts split into bounded groups |
| Private snapshot chunk | Up to 100 orders within advertised frame bound |
Process-local limits are not cluster-wide entitlements. Independent socket slots do not make same-order mutations independent: follow order versions and pending-action rules.
Current DEV professional capacity is 3,300 units/minute, with a separate
cancellation bucket at the same ceiling. Lease renew/disarm uses that control
bucket. Reads consume the ordinary profile. Every command frame is authenticated
and charged. A single request/frame costs one unit; a batch costs one per item,
including invalid or replayed items. A rate-rejected batch is not forwarded.
Honor 429 and Retry-After; reserve capacity for renewal,
reconciliation and cancellation rather than spending it all on new quotes.
Failure handling and measurement
Stop quoting on stale books, lost private recovery, unrenewed leases, restricted identity or uncertain command state. Reconcile; do not retry uncertain commands with new keys. See Errors and retries.
Disconnection does not imply cancellation. Use the lease or scope-cancel control and verify completion. During a database, venue or network outage, cancellation can be delayed; durable work survives restart and completion still requires terminal evidence.
Measure admission and exchange-effective private-feed latency separately. Retain every run, warmup, error, timeout and unsubmitted command; report p50/p99/max and counts above fixed thresholds. Published capacity requires an agreed mix of commands, fills, accounts, subscriptions and recovery scenarios.
FIX, atomic quote sets, shared multi-strategy subaccount delegation and contractual HA/SLO terms are not offered. Do not infer them from an endpoint or a short successful benchmark.
10. Cancellation and heartbeat safety
Account or market cancellation
POST /v1/orders/cancel-all
Authorization: Bearer <orders:write token>
Idempotency-Key: <durably retained unique key>
Content-Type: application/json
{"market_id":"optional-immutable-market-id"}
Use {} for all this account's markets. The operation captures an immutable
cohort under the account's admission fence and durably requests terminal
cancellation. A 202 response supplies request_id, total_orders,
remaining_orders and PENDING or COMPLETE.
Poll GET /v1/cancellations/{request_id} with orders:read until COMPLETE.
This requires terminal order evidence and no open hold/provisional reservation
for that cohort. It does not prevent future orders: stop your producers before
using it for strategy shutdown.
Retrying the original key returns the original cohort, never later quotes. Changing scope under that key returns 409. Another account cannot access this request. More than 10,000 orders returns 422; use separate market scopes. Uncertain replacements are reconciled through venue request lookup, not blindly replayed before terminal cancellation.
Opt-in heartbeat lease
cancel-on-disconnect means a missed application heartbeat, not immediate TCP
close handling. It works with REST or WebSocket commands. One lease covers the
entire financial account.
Arm with PUT /v1/cancel-on-disconnect (orders:write):
{"lease_id":"new-nonzero-uuid","timeout_seconds":30}
Wait for ARMED and record server expires_at. Renew the same ID and timeout,
for example every 10 seconds for a 30-second lease. Stop quoting if renewal
cannot be confirmed. Allow for network jitter and token renewal when choosing
your timeout.
GET /v1/cancel-on-disconnect (orders:read) returns current state. After
expiry, FIRED and cancellation_ids identify durable cancellation groups.
Poll every group's /v1/cancellations/{id} to completion. FIRED does not claim
the matching engine has already cancelled everything.
Expiry captures the current cohort atomically and splits it into groups of at most 10,000. Orders admitted after capture are not included. This is not an account freeze: all producers must honor the heartbeat-failure stop rule. Startup recovers expired leases; missing external proof keeps work pending.
Disarm and rearm
DELETE /v1/cancel-on-disconnect with {"lease_id":"current-id"} disarms an
unexpired lease; it does not cancel orders. After expiry, late renewals and
disarms return 409 and cannot undo cancellation.
To rearm, use a new UUID and previous_lease_id equal to the last lease ID.
Rearm is refused while the previous lease is armed or cancellation incomplete.
Old delayed requests cannot revive an old epoch or replace a new one. Changing
timeout requires disarm and a new epoch, not a heartbeat mutation.
11. Executions, fees and ledger reconciliation
The private feed is the live recovery authority. Bounded REST reads support independent reconciliation; none is a second matching or settlement engine.
Fee policy
GET /v1/account/trading-fees (orders:read) returns currency, server
as_of and new_order_fee_policy (id, rate_bps, rebate_bps). Policy is
pinned at NEW; replacement does not silently change it.
Maker executions have zero execution fee. Taker fees use the symmetric YES-price curve, cumulatively per taker order:
round_half_up(sum(quantity × rate_bps × price_cents × (100-price_cents)) / 1,000,000)
Each fill posts the increase in this rounded cumulative amount. Do not round fills independently. For 100 contracts at 50 cents with rate 500 bps, the fee is 125 cents, not 5% of the 5,000-cent purchase notional. Read the policy; do not hardcode today's configuration.
Posted executions
GET /v1/executions?from=<RFC3339>&to=<RFC3339>&limit=100
Required scope: orders:read. Supply a fixed half-open posting-time window
[from,to) of at most 31 days, ending no later than now. Follow next_cursor
without changing the window. Page size is at most 500. Deduplicate by fill_id,
a string preserving 64-bit precision.
Items contain your trade/order/client-order/market IDs, selected action/outcome/
price, quantity, liquidity role, fee, paired trade fee-policy ID,
ledger_entry_id and posted_at. No counterparty identity is returned. The
policy ID is the trade's taker policy, not a claim that the maker paid a fee.
Pages expose committed data, not a frozen multi-page snapshot. Re-read recent
overlapping windows after reconnect and deduplicate; a transaction can commit
after a page was read. posted_at is database posting time, not exchange time.
Missing canonical or ledger prerequisites return unavailable, not a silently
incomplete fill.
Ledger evidence and balances
GET /v1/account/ledger?entry_ids=<comma-separated-UUIDs>
GET /v1/accounts/{your-account-id}/balances
Required scope: balances:read. Ledger reads return only your postings for
1–100 exact entry IDs from executions or rebates. Interpret signed movements
in the named account, not one sum treated as spendable cash. Balance reads are
the spendable/reserved balance authority.
A settlement entry balances across all participants and system accounts. Your visible subset need not sum to zero; opposing postings are never exposed. This is not an unbounded full-history export.
Rebates
GET /v1/account/rebates?market_id=<id>&policy_id=<id>&book_day=YYYY-MM-DD
Required scope: balances:read. Rebates are allocated from a funded
market/policy/book-day pool at operator close, using the implemented weighting
rules. Book days use Europe/Malta; rebates are not immediate per-fill cash.
CREDITED returns the actual allocation and immutable ledger entry.
NOT_CREDITED has no cash amount: it is not a zero-entitlement promise,
withdrawable pending balance or forecast. Reconcile credited entries against
your own ledger postings and balance movement.
12. Onboarding and operational handoff
One approved integration maps to one confidential OAuth client, one immutable service-account subject and one dedicated financial account. Separate firms must not share credentials. Multiple processes may share an account only with coordinated order ownership, account limits and one heartbeat lease owner.
Operator checklist
- Record the participant, environment, approved responsible contact, economic terms, allowed usage and approval reference. Do not invent approvals or classify a real participant as a synthetic test identity.
- Create a least-privilege confidential client with the supplied operator utility. It disables browser/password/implicit grants, enables service accounts, restricts scope mapping and supplies the YM audience. Its returned account ID is the service-account subject; never substitute an email or another participant's ID.
- Establish that dedicated financial identity using the normal approved account controls. Record classification, legal/identity and risk evidence, and limits. Client creation alone neither approves KYC nor enables trading, creates funds, waives RG checks or rewrites another user's identity.
- For DEV UAT, use an explicitly classified synthetic TEST_PLAYER and balanced test funding. For real participants, complete the applicable identity, legal and funding process; test-fixture provisioning is not that process.
- Hand over environment URLs, account/client IDs, granted scopes, supported markets, rate profile, fee/rebate policy, credential delivery channel and operational contact. Deliver the secret separately through a secret manager.
- Run the acceptance sequence below; retain exact revision and results. Do not approve production use from DEV documentation or an unverified code branch.
Operator utility
The operator utilities and reference clients belong to the qualified DEV
release, not the docs-only publication on main. YM supplies the matching
version during onboarding. From that release's Backend/scripts/market-maker,
review the plan first:
node onboarding.cjs plan ym-mm-firm-strategy approval-reference
An authorized operator supplies MM_AUTH_ORIGIN, MM_ADMIN_TOKEN and
MM_SECRET_OUTPUT through protected process configuration. The utility is
intentionally DEV-only in this qualification release. create provisions the
client and writes its secret to a new owner-only file; stdout contains only the
nonsecret manifest. It refuses to overwrite an existing client or secret file.
rotate issues a new secret; disable blocks new token issuance. It does not
automatically alter financial identities, activate trading or fund an account.
node onboarding.cjs create ym-mm-firm-strategy approval-reference
node onboarding.cjs rotate ym-mm-firm-strategy
node onboarding.cjs disable ym-mm-firm-strategy
Participant UAT acceptance
The read-only doctor.cjs checks token/account binding, reads and all three
WebSocket handshakes without submitting commands. Set YM_BASE_URL,
YM_ISSUER, YM_CLIENT_ID, YM_CLIENT_SECRET, YM_ACCOUNT_ID and YM_MARKETS
(comma-separated approved market IDs), then run node doctor.cjs. Its output
contains no credentials. This is a smoke check, not the complete acceptance
sequence. book.cjs is a tested reference consumer for sequence validation,
absolute quantities and atomic book changes.
- Acquire/renew tokens; verify wrong-secret and missing-scope rejection and that another account's reads/cursor cannot be used.
- Discover the approved canonical markets and generations; establish a fresh book and completed private snapshot/replay before commands.
- NEW → working → REPLACE → effective → CANCEL → terminal/released. Replay exact keys after an uncertain response; changed payloads must not duplicate.
- Verify YES and NO price coordinates, partial/full fills and cumulative fees; reconcile posted executions, own ledger movements and current balances.
- Test noncrossing and crossing post-only, including replacement cancellation.
- Test mixed batch outcomes and per-item retry, not only HTTP status 200.
- Disconnect/reconnect public and private feeds, reject sequence gaps, recover snapshots/cursors, and stop quoting while freshness is unavailable.
- Prove scope cancellation excludes other accounts/markets/later orders. Miss a lease heartbeat, verify every captured order terminal and holds released, then rearm explicitly. A later heartbeat must not undo expiry.
- Rotate a disposable client's secret; old issuance must fail, new issuance and token scopes must work. Separately demonstrate financial containment.
- Measure latency with the intended connections, renewal and reconciliation workload; retain failures and threshold counts, not only the best run.
Emergency containment and offboarding
Stop quote producers, freeze/restrict the financial account with the existing audited operator control, request terminal cancellation and verify orders and holds. Disable OAuth issuance and revoke sessions as applicable. Rotation or client disable alone is not instantaneous invalidation of cached bearer tokens. Retain immutable financial records; never delete a traded account as cleanup.
Support handoff must include an actual reachable contact and an agreed response process. This document does not manufacture a staffed 24/7 desk or contractual SLA. Include environment, UTC timestamps, request/command IDs, stream/cursor context and sanitized errors in incidents; never include secrets.
Endpoint reference
39 operations. Expand an endpoint for parameters, authentication and exact request/response shapes. Component references resolve in the schema section and downloadable OpenAPI.
GET/v1/categoriesCanonical top-level prediction-market categories
Canonical top-level prediction-market categories
Scopes: public
Request and response contract
responses:
"200":
description: Public canonical categories from the active taxonomy registry
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalTaxonomyListResponse"
GET/v1/taxonomyCanonical public taxonomy hierarchy
Canonical public taxonomy hierarchy
Scopes: public
Request and response contract
responses:
"200":
description: Public taxonomy nodes with explicit parent references
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalTaxonomyListResponse"
GET/v1/seriesCanonical recurring series nodes
Canonical recurring series nodes
Scopes: public
Request and response contract
responses:
"200":
description: Public series nodes from the active taxonomy registry
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalTaxonomyListResponse"
GET/v1/sportsCanonical sports taxonomy nodes
Canonical sports taxonomy nodes
Scopes: public
Request and response contract
responses:
"200":
description: Public sport nodes from the active taxonomy registry
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalTaxonomyListResponse"
GET/v1/competitionsCanonical sports competitions
Canonical sports competitions
Scopes: public
| Name | Location | Required | Contract |
|---|---|---|---|
sport | query | no | {"type":"string"} |
q | query | no | {"type":"string"} |
limit | query | no | {"type":"integer","minimum":1,"maximum":500} |
Request and response contract
responses:
"200":
description: Public competitions with immutable entity ids and parent references
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalCompetitionListResponse"
GET/v1/eventsCanonical event-first market discovery
The recommended discovery entry point. Results use immutable YM ids, human slugs, explicit parent references, public revisions, and opaque keyset cursors. Offset pagination is deliberately unsupported.
Scopes: public
| Name | Location | Required | Contract |
|---|---|---|---|
category | query | no | Comma-separated canonical category keys. |
sport | query | no | {"type":"string"} |
competition | query | no | {"type":"string"} |
series | query | no | {"type":"string"} |
cycle | query | no | {"type":"string"} |
phase | query | no | {"type":"string"} |
tag | query | no | Comma-separated tag ids or slugs. |
status | query | no | Comma-separated lifecycle statuses or public aliases (`active`,`pending`,`resolved`). |
q | query | no | {"type":"string"} |
start_min | query | no | {"type":"string","format":"date-time"} |
start_max | query | no | {"type":"string","format":"date-time"} |
close_min | query | no | {"type":"string","format":"date-time"} |
close_max | query | no | {"type":"string","format":"date-time"} |
include | query | no | Supports `families`, `families.markets`, and explicit `external_mappings`. |
cursor | query | no | Opaque `next_cursor` from the preceding page. |
limit | query | no | {"type":"integer","minimum":1,"maximum":200} |
Request and response contract
responses:
"200":
description: Canonical event page
headers:
ETag:
schema:
type: string
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalEventListResponse"
"304":
description: Representation matches `If-None-Match`.
"400":
description: Invalid filter, include, timestamp, status, cursor, or unsupported offset.
GET/v1/events/{eventID}Canonical event detail by immutable id, current slug, or permanent alias
Canonical event detail by immutable id, current slug, or permanent alias
Scopes: public
| Name | Location | Required | Contract |
|---|---|---|---|
eventID | path | yes | {"type":"string"} |
include | query | no | Supports `families`, `families.markets`, and explicit `external_mappings`. |
Request and response contract
responses:
"200":
description: Canonical event detail. `Content-Location` contains the current
canonical route.
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalEventResponse"
"404":
description: Event not found.
GET/v1/events/{eventID}/market-familiesCanonical market families nested under an event
Canonical market families nested under an event
Scopes: public
| Name | Location | Required | Contract |
|---|---|---|---|
eventID | path | yes | {"type":"string"} |
include | query | no | Supports `families.markets` and explicit `external_mappings`. |
Request and response contract
responses:
"200":
description: Event-scoped canonical market families
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalFamilyListResponse"
"404":
description: Event not found.
GET/v1/market-familiesGrouped market discovery with legacy or canonical hierarchy view
Grouped market discovery with legacy or canonical hierarchy view
Scopes: public
| Name | Location | Required | Contract |
|---|---|---|---|
view | query | no | Use `canonical` for immutable identity, revisions, and canonical routes. Omission preserves the legacy response during migration. |
event | query | no | Canonical view only; filter by immutable event id, slug, or permanent alias. |
include | query | no | Canonical view only; supports `families.markets` and `external_mappings`. |
cursor | query | no | Canonical view only; opaque keyset cursor. Offset pagination is unsupported. |
status | query | no | Optional comma-separated family statuses (`active`,`resolved`,`pending`) |
category | query | no | Optional comma-separated lower-case category filter |
entity_id | query | no | Optional canonical sports entity id filter |
entity_slug | query | no | Optional canonical sports entity slug filter |
limit | query | no | {"type":"integer","minimum":1,"maximum":500} |
Request and response contract
responses:
"200":
description: Market families
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/MarketFamilyListResponse"
- $ref: "#/components/schemas/CanonicalDirectFamilyListResponse"
"400":
description: Invalid canonical filter, include, status, cursor, or unsupported offset.
GET/v1/market-families/{familyID}Market family detail with legacy or canonical hierarchy view
Market family detail with legacy or canonical hierarchy view
Scopes: public
| Name | Location | Required | Contract |
|---|---|---|---|
familyID | path | yes | {"type":"string"} |
view | query | no | {"type":"string","enum":["legacy","canonical"]} |
include | query | no | Canonical view only; provider ids require explicit `external_mappings`. |
Request and response contract
responses:
"200":
description: Market family detail
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/MarketFamilyResponse"
- $ref: "#/components/schemas/CanonicalFamilyResponse"
"400":
description: Ambiguous family slug or invalid canonical include.
"404":
description: Market family not found
GET/v1/marketsPublic market catalog with legacy quote view or canonical hierarchy view
Public market catalog with legacy quote view or canonical hierarchy view
Scopes: public
| Name | Location | Required | Contract |
|---|---|---|---|
view | query | no | Use `canonical` for immutable event/family identity, revision, and canonical-route fields. Omission preserves the legacy response during migration. |
event | query | no | Canonical view only; immutable event id, current slug, or permanent event alias. |
family | query | no | Canonical view only; immutable family id or an event-scoped family slug/alias. |
include | query | no | Canonical view only; comma-separated expansion list. Provider ids require explicit `external_mappings`. |
cursor | query | no | Canonical view only; opaque keyset cursor. Offset pagination is unsupported. |
status | query | no | Optional comma-separated lifecycle filter. Supports raw backend statuses and frontend aliases (`active`,`pending`,`resolved`). |
category | query | no | Optional comma-separated lower-case category filter. |
country | query | no | Optional comma-separated lower-case country filter. |
limit | query | no | {"type":"integer","minimum":1,"maximum":500} |
Request and response contract
responses:
"200":
description: Markets
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/LegacyMarketListResponse"
- $ref: "#/components/schemas/CanonicalMarketListResponse"
"400":
description: Invalid canonical filter, include, status, cursor, or unsupported offset.
GET/v1/markets/{marketID}Public market detail with legacy quote or canonical hierarchy fields
Public market detail with legacy quote or canonical hierarchy fields
Scopes: public
| Name | Location | Required | Contract |
|---|---|---|---|
marketID | path | yes | {"type":"string"} |
view | query | no | Use `canonical` for immutable event/family identity, revision, and canonical URL. |
include | query | no | Canonical view only; provider ids require explicit `external_mappings`. |
Request and response contract
responses:
"200":
description: Market detail
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/LegacyMarket"
- $ref: "#/components/schemas/CanonicalMarketResponse"
"400":
description: Invalid canonical include.
"404":
description: Market not found
GET/v1/markets/{marketID}/bookL2 book snapshot
L2 book snapshot
Scopes: public
| Name | Location | Required | Contract |
|---|---|---|---|
marketID | path | yes | {"type":"string"} |
depth | query | no | Maximum price levels returned per side. Values above 200 are capped at 200. |
Request and response contract
responses:
"200":
description: Book
content:
application/json:
schema:
$ref: "#/components/schemas/BookSnapshot"
GET/v1/bookBook snapshot by query params
Book snapshot by query params
Scopes: public
| Name | Location | Required | Contract |
|---|---|---|---|
market_id | query | yes | {"type":"string"} |
depth | query | no | Maximum price levels returned per side. Values above 200 are capped at 200. |
Request and response contract
responses:
"200":
description: Book
content:
application/json:
schema:
$ref: "#/components/schemas/BookSnapshot"
GET/v1/markets/{marketID}/statsMarket statistics derived from settled fills and open holds
Market statistics derived from settled fills and open holds
Scopes: public
| Name | Location | Required | Contract |
|---|---|---|---|
marketID | path | yes | {"type":"string"} |
Request and response contract
responses:
"200":
description: Market stats
content:
application/json:
schema:
type: object
required:
- market_id
- volume_24h
- total_volume
- traders_count_24h
- trades_count_24h
- avg_position_24h
- open_interest
properties:
market_id:
type: string
volume_24h:
type: number
total_volume:
type: number
traders_count_24h:
type: integer
trades_count_24h:
type: integer
avg_position_24h:
type: number
open_interest:
type: number
GET/v1/markets/{marketID}/tradesRecent taker-side market trades
Recent taker-side market trades
Scopes: public
| Name | Location | Required | Contract |
|---|---|---|---|
marketID | path | yes | {"type":"string"} |
limit | query | no | {"type":"integer","minimum":1,"maximum":200} |
Request and response contract
responses:
"200":
description: Recent trades
content:
application/json:
schema:
type: object
required:
- market_id
- count
- trades
properties:
market_id:
type: string
count:
type: integer
trades:
type: array
items:
type: object
required:
- trade_id
- market_id
- user_id
- username
- counterparty_user_id
- side
- liquidity_role
- price
- price_cents
- quantity
- occurred_at
properties:
trade_id:
type: string
market_id:
type: string
user_id:
type: string
description: Pseudonymous participant identifier
username:
type: string
description: Pseudonymous display identifier
counterparty_user_id:
type: string
description: Pseudonymous participant identifier for the opposite side
side:
type: string
enum:
- BUY
- SELL
liquidity_role:
type: string
enum:
- TAKER
- MAKER
price:
type: number
price_cents:
type: integer
quantity:
type: integer
occurred_at:
type: string
format: date-time
GET/v1/ordersList the authenticated participant's canonical orders
List the authenticated participant's canonical orders
Scopes: orders:read
| Name | Location | Required | Contract |
|---|---|---|---|
market_id | query | no | {"type":"string"} |
execution_status | query | no | {"type":"string","enum":["PENDING","WORKING","PARTIALLY_FILLED","FILLED","CANCELLED","EXPIRED","REJECTED"]} |
pending_action | query | no | {"type":"string","enum":["NONE","REPLACE","CANCEL"]} |
state_quality | query | no | {"type":"string","enum":["CONFIRMED","RECONCILIATION_REQUIRED"]} |
cursor | query | no | Opaque newest-first keyset cursor returned by the prior page. |
limit | query | no | {"type":"integer","minimum":1,"maximum":500} |
Request and response contract
responses:
"200":
description: Stable newest-first canonical order page
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalOrderListResponse"
"400":
description: Invalid canonical filter or cursor
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalOrderError"
"503":
description: One or more selected orders require reconciliation before canonical
read cutover
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalOrderError"
POST/v1/ordersAdmit a canonical order command
Admit a canonical order command
Scopes: orders:write
| Name | Location | Required | Contract |
|---|---|---|---|
Idempotency-Key | header | yes | Participant-scoped command identity; the `ym:` prefix is reserved for internal commands. |
Request and response contract
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateOrderRequest"
responses:
"200":
description: Exact scoped idempotent replay with the original command result and
current order snapshot
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalCommandOrderResponse"
"201":
description: Command admitted atomically with a PENDING canonical order
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalCommandOrderResponse"
example:
schema_version: 2
command:
command_id: 9b1b889c-2fd3-46d6-bc55-441afc7a7cc8
order_id: 3bdb9f66-402e-4f06-9bc7-74026c004317
command_sequence: 1
command_type: NEW
client_request_id: mm-new-no-000001
client_order_id: strategy-no-000001
admission_status: ADMITTED
outcome_status: PENDING
admitted_at: 2026-09-02T10:00:00Z
created_at: 2026-09-02T10:00:00Z
updated_at: 2026-09-02T10:00:00Z
order:
id: 3bdb9f66-402e-4f06-9bc7-74026c004317
created_by_command_id: 9b1b889c-2fd3-46d6-bc55-441afc7a7cc8
client_order_id: strategy-no-000001
market_id: example-market
market_generation: 7
action: BUY
outcome: NO
price_cents: 35
original_quantity: 10
total_quantity: 10
cumulative_filled_quantity: 0
remaining_unfilled_quantity: 10
working_quantity: 0
filled_notional_cents: 0
execution_status: PENDING
pending_action: null
state_quality: CONFIRMED
order_version: 1
is_terminal: false
is_working: false
may_still_execute: true
has_reserved_exposure: true
time_in_force: GTC
admitted_at: 2026-09-02T10:00:00Z
updated_at: 2026-09-02T10:00:00Z
idempotent_replay: false
"400":
description: Invalid command
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalOrderError"
"409":
description: Stale generation, idempotency conflict, command conflict, or
unavailable market state
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalOrderError"
"503":
description: Temporarily unavailable admission dependency
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalOrderError"
GET/v1/orders/{orderID}Get order
Get order
Scopes: orders:read
| Name | Location | Required | Contract |
|---|---|---|---|
orderID | path | yes | {"type":"string","format":"uuid"} |
Request and response contract
responses:
"200":
description: Current authoritative canonical order snapshot
headers:
ETag:
description: Derived from order_version; accepted by If-None-Match.
schema:
type: string
content:
application/json:
schema:
$ref: "#/components/schemas/Order"
"304":
description: The supplied ETag matches the current order version
"503":
description: The order requires canonical reconciliation
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalOrderError"
POST/v1/orders/{orderID}/replaceAdmit a canonical single-order replacement command
Admit a canonical single-order replacement command
Scopes: orders:write
| Name | Location | Required | Contract |
|---|---|---|---|
Idempotency-Key | header | yes | {"type":"string","maxLength":200,"pattern":"^(?![Yy][Mm]:).+quot;} |
orderID | path | yes | {"type":"string","format":"uuid"} |
Request and response contract
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ReplaceOrderRequest"
responses:
"200":
description: Exact idempotent replay of the original replacement result
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalCommandOrderResponse"
"202":
description: Replacement admitted; the prior price and quantity remain effective
until DX confirms REPLACED
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalCommandOrderResponse"
"409":
description: Version conflict, no-op replacement, insufficient exposure, or
another mutation is pending
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalOrderError"
POST/v1/orders/{orderID}/cancelAdmit a canonical cancel command
Admit a canonical cancel command
Scopes: orders:write
| Name | Location | Required | Contract |
|---|---|---|---|
Idempotency-Key | header | yes | Participant-scoped command identity; the `ym:` prefix is reserved for internal commands. |
orderID | path | yes | {"type":"string","format":"uuid"} |
Request and response contract
responses:
"200":
description: Cancel is already effective locally or is an idempotent
completed/no-op result
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalCommandOrderResponse"
"202":
description: Venue-directed cancel admitted; order truth retains its prior
execution status with pending_action=CANCEL
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalCommandOrderResponse"
"409":
description: Idempotency conflict or another mutation command is already pending
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalOrderError"
"503":
description: Order requires reconciliation before a safe cancel decision
content:
application/json:
schema:
$ref: "#/components/schemas/CanonicalOrderError"
POST/v1/orders/batchCreate multiple limit orders
DEV-qualified per-item NEW admission and exact replay. Up to 250 independent outcomes; not an atomic quote set. Each item consumes one rate unit, including rejected and replayed items.
Scopes: orders:write
Request and response contract
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- orders
properties:
orders:
type: array
minItems: 1
maxItems: 250
items:
$ref: "#/components/schemas/CreateOrderBatchItem"
responses:
"200":
description: Per-item batch result
content:
application/json:
schema:
$ref: "#/components/schemas/OrderBatchResponse"
POST/v1/orders/cancel-batchCancel multiple existing orders
DEV-qualified per-item CANCEL admission and exact replay. Up to 250 independent outcomes; inspect every result. Each item consumes one cancellation rate unit, including rejected and replayed items.
Scopes: orders:write
Request and response contract
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- orders
properties:
orders:
type: array
minItems: 1
maxItems: 250
items:
type: object
required:
- order_id
- client_request_id
properties:
order_id:
type: string
format: uuid
client_request_id:
type: string
maxLength: 200
pattern: ^(?![Yy][Mm]:).+$
responses:
"200":
description: Per-item batch result
content:
application/json:
schema:
$ref: "#/components/schemas/OrderBatchResponse"
POST/v1/orders/cancel-allDurably cancel a fixed cohort of this account's outstanding orders
Omitting market_id selects the whole authenticated account. The request captures at most 10000 order IDs; retries never include newer orders. HTTP 202 means durable admission, not completed cancellation. Fills that win the race remain valid. Read the cancellation resource until COMPLETE, and reconcile executions through the private trading feed. This is not an account freeze or a liquidation of filled positions.
Scopes: orders:write
| Name | Location | Required | Contract |
|---|---|---|---|
Idempotency-Key | header | yes | {"type":"string","minLength":1,"maxLength":200} |
Request and response contract
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
properties:
market_id:
type: string
maxLength: 500
responses:
"202":
description: Durable cancellation admitted or replayed
content:
application/json:
schema:
$ref: "#/components/schemas/CancellationStatus"
"409":
description: The same key was used with a different market scope
"422":
description: Scope exceeds 10000 orders; divide by market
"503":
description: Retry the identical request and key; do not infer cancellation
GET/v1/cancellations/{requestID}Read this account's cancellation progress
Read this account's cancellation progress
Scopes: orders:read
| Name | Location | Required | Contract |
|---|---|---|---|
requestID | path | yes | {"type":"string","format":"uuid"} |
Request and response contract
responses:
"200":
description: COMPLETE requires terminal state and released unmatched collateral
for every captured order
content:
application/json:
schema:
$ref: "#/components/schemas/CancellationStatus"
"404":
description: Unknown cancellation or a different account's request
GET/v1/cancel-on-disconnectRead the authenticated account's opt-in heartbeat lease
Read the authenticated account's opt-in heartbeat lease
Scopes: orders:read
Request and response contract
responses:
"200":
description: ARMED is a deadline, FIRED means cancellation admitted, not yet
necessarily complete
content:
application/json:
schema:
$ref: "#/components/schemas/CancellationLease"
"404":
description: No lease has been armed
PUT/v1/cancel-on-disconnectArm or renew an account-wide dead-man cancellation lease
Opt-in and independent of transport. Renew before expiry with the same lease_id and timeout. Missing heartbeats trigger durable cancellation of outstanding orders, not liquidation or permanent account freezing. Heartbeats do not place or replace orders. After expiry, wait for the every resource in cancellation_ids to be COMPLETE, then use a new lease_id and set previous_lease_id to the current lease_id from GET. Delayed old heartbeats cannot revive an expired or disarmed epoch. Close sockets alone does not disarm it.
Scopes: orders:write
Request and response contract
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required:
- lease_id
- timeout_seconds
properties:
lease_id:
type: string
format: uuid
previous_lease_id:
type: string
format: uuid
timeout_seconds:
type: integer
minimum: 10
maximum: 120
responses:
"200":
description: Committed current lease; start quoting only after this acknowledgement
content:
application/json:
schema:
$ref: "#/components/schemas/CancellationLease"
"409":
description: Expired
disarmed: null
changed epoch or pending prior cancellation; read state: null
"503":
description: Outcome unconfirmed; read state and stop quoting until protection confirmed
DELETE/v1/cancel-on-disconnectExplicitly disarm a still-current unexpired heartbeat lease
Explicitly disarm a still-current unexpired heartbeat lease
Scopes: orders:write
Request and response contract
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required:
- lease_id
properties:
lease_id:
type: string
format: uuid
responses:
"200":
description: Disarmed; remaining orders are NOT cancelled by disarm
content:
application/json:
schema:
$ref: "#/components/schemas/CancellationLease"
"409":
description: Stale lease identity or already expired; cancellation cannot be revoked
GET/v1/order-entryPersistent authenticated NEW, REPLACE, CANCEL and CANCEL_ALL command transport
WebSocket contract ym.order-entry.v1. Send text JSON with request_id, operation, client_request_id, optional order_id and body matching the corresponding REST request. Each command.response contains request_id, the REST status and unmodified JSON body. Responses may arrive out of order. Admission is not execution: consume /v1/trading-feed separately. The same per-command scope, revocation and rate rules apply as REST. Limits per connection are 12 concurrent NEW/REPLACE and 4 reserved cancellation requests; no queue. Maximum input frame is 16 KiB. Slow readers disconnect. Reconnect on token expiry; retry unknown outcomes with identical client_request_id and payload. No automatic replay.
Scopes: orders:write
Request and response contract
responses:
"101":
description: WebSocket connected; first frame is stream.welcome
"400":
description: Query parameters are not supported
"401":
description: Invalid or missing authentication
"403":
description: Missing scope or disallowed browser origin
"429":
description: Connection limit or handshake rate limit
GET/v1/market-feedProfessional order-book snapshots and incremental updates
Professional order-book snapshots and incremental updates
Scopes: orders:read
| Name | Location | Required | Contract |
|---|---|---|---|
markets | query | yes | Comma-separated immutable market IDs; 1 to 100 distinct IDs, no wildcard. |
Request and response contract
responses:
"101":
description: Authenticated WebSocket. Every connection starts with snapshots;
reconnect replaces all local books.
"400":
description: Invalid or unsupported subscription parameters.
"401":
description: Authentication required.
"403":
description: Insufficient scope or disallowed browser origin.
"404":
description: Unknown market.
"429":
description: Connection or request limit exceeded.
GET/v1/trading-feedOpen the resumable private order and execution WebSocket feed
This endpoint serves Private Order & Execution Feed v2. A v1 cursor cannot resume v2; after durably completing v1 through the announced boundary, begin with a cursorless snapshot. That snapshot replaces only nonterminal state and does not replay v1 terminal orders or fills. The gateway authenticates the bearer token and signs participant identity to the dedicated private-trading-feed service. Identity is never selected by query string. Without a cursor the stream sends a consistent nonterminal snapshot before live events. With a cursor it resumes at-least-once delivery after the last client-applied event. WebSocket compression is disabled for this low-latency profile. The route is absent until PRIVATE_TRADING_FEED_ENABLED is explicitly enabled after the documented durability, replica, retention, and canonical-readiness rollout gates pass.
Scopes: trading-feed:read
| Name | Location | Required | Contract |
|---|---|---|---|
cursor | query | no | Opaque signed last-applied cursor from this participant's prior session. |
Request and response contract
responses:
"101":
description: WebSocket protocol switch; message schemas are documented by
x-private-trading-feed below.
"401":
description: Missing or invalid bearer authentication
"403":
description: Missing trading-feed scope or disallowed browser Origin
"503":
description: Private feed unavailable
GET/v1/executionsPaginated financially posted fills in the order's YES/NO coordinate
Self-scoped, bounded to a 31-day window and 500 fills per page. Ordered by posting timestamp and fill ID descending. Pass next_cursor with exactly the same from/to window. No counterparty identity is returned. Each page sees committed data, not a cross-page database snapshot; re-read recent overlapping windows and deduplicate fill_id to cover in-flight commits. This is reconciliation history, not an ordered private-feed replacement.
Scopes: orders:read
| Name | Location | Required | Contract |
|---|---|---|---|
from | query | yes | {"type":"string","format":"date-time"} |
to | query | yes | {"type":"string","format":"date-time"} |
limit | query | no | {"type":"integer","minimum":1,"maximum":500,"default":100} |
cursor | query | no | {"type":"string"} |
Request and response contract
responses:
"200":
description: Posted fills; empty next_cursor means no further currently visible page
content:
application/json:
schema:
type: object
required:
- schema_version
- user_id
- from
- to
- executions
- next_cursor
properties:
schema_version:
const: 2
user_id:
type: string
from:
type: string
format: date-time
to:
type: string
format: date-time
next_cursor:
type: string
consistency:
type: string
executions:
type: array
items:
$ref: "#/components/schemas/PostedExecution"
"400":
description: Invalid window
cursor: null
limit or query parameter: null
"503":
description: Financial or canonical projection incomplete; not an empty
successful history
GET/v1/account/trading-feesCurrent new-order fee policy and account book currency
Current new-order fee policy and account book currency
Scopes: orders:read
Request and response contract
responses:
"200":
description: Existing orders retain their admitted policy; no new fee is charged
by this read
content:
application/json:
schema:
type: object
required:
- user_id
- currency
- as_of
- new_order_fee_policy
- policy_binding
properties:
user_id:
type: string
currency:
type: string
as_of:
type: string
format: date-time
new_order_fee_policy:
type: object
required:
- id
- rate_bps
- rebate_bps
properties:
id:
type: integer
rate_bps:
type: integer
rebate_bps:
type: integer
policy_binding:
type: string
fee_formula:
type: string
maker_execution_fee_cents:
type: integer
rebate:
type: string
rebate_cash_authority:
type: string
GET/v1/account/rebatesRead this account's credited allocation for a named maker-rebate pool
Read this account's credited allocation for a named maker-rebate pool
Scopes: balances:read
| Name | Location | Required | Contract |
|---|---|---|---|
market_id | query | yes | {"type":"string"} |
policy_id | query | yes | {"type":"integer","minimum":1} |
book_day | query | yes | {"type":"string","format":"date"} |
Request and response contract
responses:
"200":
description: NOT_CREDITED is not a zero entitlement or a withdrawable accrual
content:
application/json:
schema:
type: object
required:
- status
- market_id
- policy_id
- book_day
- amount_cents
properties:
status:
type: string
enum:
- CREDITED
- NOT_CREDITED
market_id:
type: string
policy_id:
type: integer
book_day:
type: string
format: date
amount_cents:
type:
- integer
- "null"
format: int64
ledger_entry_id:
type: string
format: uuid
posted_at:
type: string
format: date-time
GET/v1/account/ledgerThis account's immutable postings for named financial entries
Supply ledger entry IDs returned by executions, credited rebates or payment evidence. Only postings whose account_code belongs to the authenticated participant are returned. Another participant's entry ID returns no data. No all-history scan or mutable balance reconstruction. The participant subset need not sum to zero; the complete internal journal includes counterparties and platform accounts and balances.
Scopes: balances:read
| Name | Location | Required | Contract |
|---|---|---|---|
entry_ids | query | yes | Comma-separated list of 1 to 100 entry UUIDs |
Request and response contract
responses:
"200":
description: Participant-owned postings only
content:
application/json:
schema:
type: object
required:
- user_id
- postings
- scope
properties:
user_id:
type: string
scope:
type: string
postings:
type: array
items:
type: object
required:
- posting_id
- entry_id
- entry_type
- account_code
- amount_cents
- posted_at
properties:
posting_id:
type: string
entry_id:
type: string
format: uuid
entry_type:
type: string
account_code:
type: string
amount_cents:
type: integer
format: int64
posted_at:
type: string
format: date-time
"422":
description: More than 10000 postings; split entry IDs into smaller requests
GET/v1/positionsList user positions (YES/NO exposure projection)
List user positions (YES/NO exposure projection)
Scopes: orders:read
| Name | Location | Required | Contract |
|---|---|---|---|
user_id | query | no | {"type":"string"} |
market_id | query | no | {"type":"string"} |
Request and response contract
responses:
"200":
description: Positions listed
content:
application/json:
schema:
type: object
required:
- user_id
- count
- positions
properties:
user_id:
type: string
count:
type: integer
positions:
type: array
items:
type: object
required:
- market_id
- yes_shares
- no_shares
- avg_yes_price
- avg_no_price
- mark_yes_price
- mark_no_price
- unrealized_pnl
- realized_pnl
- market
properties:
market_id:
type: string
yes_shares:
type: integer
no_shares:
type: integer
avg_yes_price:
type: number
avg_no_price:
type: number
mark_yes_price:
type: number
mark_no_price:
type: number
unrealized_pnl:
type: number
realized_pnl:
type: number
market:
type: object
required:
- market_id
- title
- description
- status
- settlement_currency
- updated_at
- metadata
properties:
market_id:
type: string
title:
type: string
description:
type: string
status:
type: string
enum:
- DRAFT
- OPEN
- SUSPENDED
- RESOLVED
- CLOSED
settlement_currency:
type: string
close_at:
type: string
format: date-time
nullable: true
resolve_at:
type: string
format: date-time
nullable: true
resolved_outcome:
type: string
nullable: true
updated_at:
type: string
format: date-time
nullable: true
metadata:
type: object
additionalProperties: true
GET/v1/portfolioUser portfolio summary and positions
User portfolio summary and positions
Scopes: orders:read
| Name | Location | Required | Contract |
|---|---|---|---|
user_id | query | no | {"type":"string"} |
market_id | query | no | {"type":"string"} |
Request and response contract
responses:
"200":
description: Portfolio summary
content:
application/json:
schema:
type: object
required:
- user_id
- total_value
- total_pnl
- pnl_percent
- positions
properties:
user_id:
type: string
total_value:
type: number
total_pnl:
type: number
pnl_percent:
type: number
positions:
type: array
items:
type: object
required:
- market_id
- yes_shares
- no_shares
- avg_yes_price
- avg_no_price
- mark_yes_price
- mark_no_price
- unrealized_pnl
- realized_pnl
- market
properties:
market_id:
type: string
yes_shares:
type: integer
no_shares:
type: integer
avg_yes_price:
type: number
avg_no_price:
type: number
mark_yes_price:
type: number
mark_no_price:
type: number
unrealized_pnl:
type: number
realized_pnl:
type: number
market:
type: object
required:
- market_id
- title
- description
- status
- settlement_currency
- updated_at
- metadata
properties:
market_id:
type: string
title:
type: string
description:
type: string
status:
type: string
enum:
- DRAFT
- OPEN
- SUSPENDED
- RESOLVED
- CLOSED
settlement_currency:
type: string
close_at:
type: string
format: date-time
nullable: true
resolve_at:
type: string
format: date-time
nullable: true
resolved_outcome:
type: string
nullable: true
updated_at:
type: string
format: date-time
nullable: true
metadata:
type: object
additionalProperties: true
GET/v1/accounts/{userID}/balancesUser balances (derived from immutable postings)
User balances (derived from immutable postings)
Scopes: balances:read
| Name | Location | Required | Contract |
|---|---|---|---|
userID | path | yes | {"type":"string"} |
Request and response contract
responses:
"200":
description: Balances
Shared schemas
CanonicalTaxonomyListResponse
type: object
additionalProperties: false
required:
- count
- source
properties:
taxonomy:
type: array
items:
$ref: "#/components/schemas/CanonicalTaxonomyNode"
categories:
type: array
items:
$ref: "#/components/schemas/CanonicalTaxonomyNode"
series:
type: array
items:
$ref: "#/components/schemas/CanonicalTaxonomyNode"
sports:
type: array
items:
$ref: "#/components/schemas/CanonicalTaxonomyNode"
count:
type: integer
source:
type: string
enum:
- canonical_catalog
CanonicalTaxonomyNode
type: object
additionalProperties: false
required:
- id
- node_key
- kind
- category
- label
- slug
- path
- depth
- sort_order
- registry_version
- canonical_url
properties:
id:
type: string
node_key:
type: string
parent_id:
type: string
kind:
type: string
group_kind:
type: string
category:
type: string
label:
type: string
slug:
type: string
path:
type: string
depth:
type: integer
sort_order:
type: integer
registry_version:
type: string
canonical_url:
type: string
metadata:
type: object
additionalProperties: true
CanonicalTagListResponse
type: object
additionalProperties: false
required:
- tags
- count
- source
properties:
tags:
type: array
items:
$ref: "#/components/schemas/CanonicalTag"
count:
type: integer
source:
type: string
enum:
- canonical_catalog
CanonicalTag
type: object
additionalProperties: false
required:
- id
- slug
- label
- event_count
properties:
id:
type: string
slug:
type: string
label:
type: string
category:
type: string
event_count:
type: integer
format: int64
metadata:
type: object
additionalProperties: true
CanonicalCompetitionListResponse
type: object
additionalProperties: false
required:
- competitions
- count
- source
properties:
competitions:
type: array
items:
type: object
additionalProperties: true
count:
type: integer
source:
type: string
enum:
- canonical_catalog
CanonicalEventListResponse
type: object
additionalProperties: false
required:
- events
- count
- limit
- has_more
- next_cursor
- source
properties:
events:
type: array
items:
$ref: "#/components/schemas/CanonicalEvent"
count:
type: integer
limit:
type: integer
has_more:
type: boolean
next_cursor:
type: string
source:
type: string
enum:
- canonical_catalog
CanonicalEvent
type: object
additionalProperties: false
required:
- id
- slug
- title
- description
- status
- classification_status
- classification_source
- taxonomy
- tags
- revision
- updated_at
- canonical_url
properties:
id:
type: string
slug:
type: string
title:
type: string
description:
type: string
status:
type: string
classification_status:
type: string
classification_source:
type: string
starts_at:
type: string
format: date-time
close_at:
type: string
format: date-time
resolve_at:
type: string
format: date-time
taxonomy:
$ref: "#/components/schemas/CanonicalEventTaxonomy"
tags:
type: array
items:
$ref: "#/components/schemas/CanonicalTag"
revision:
type: integer
format: int64
updated_at:
type: string
format: date-time
canonical_url:
type: string
live:
$ref: "#/components/schemas/LivePresentationSnapshot"
market_families:
type: array
items:
$ref: "#/components/schemas/CanonicalMarketFamily"
CanonicalEventTaxonomy
type: object
additionalProperties: false
required:
- category
- breadcrumbs
properties:
category:
type: string
sport:
type: string
competition:
type: string
series:
type: string
cycle:
type: string
phase:
type: string
path_pattern_id:
type: string
node_id:
type: string
node_key:
type: string
path:
type: string
breadcrumbs:
type: array
items:
$ref: "#/components/schemas/CanonicalTaxonomyNode"
LivePresentationSnapshot
type: object
additionalProperties: false
required:
- provider
- providerScopeId
- sport
- schemaKind
- isLive
- isTerminal
- freshness
- competitors
- periods
- clock
- summary
- sourceCursors
- observedAt
properties:
eventId:
type: string
bindingHash:
type: string
provider:
type: string
providerScopeId:
type: string
sport:
type: string
schemaKind:
type: string
enum:
- TEAM_GAME
- TENNIS_MATCH
- GOLF_LEADERBOARD
- NONE
providerStatusName:
type: string
isLive:
type: boolean
isTerminal:
type: boolean
freshness:
type: string
enum:
- PRE_EVENT
- FRESH
- STALE
- TERMINAL
- UNAVAILABLE
startedAt:
type: string
format: date-time
endedAt:
type: string
format: date-time
competitors:
type: array
items:
$ref: "#/components/schemas/LivePresentationCompetitor"
periods:
type: array
items:
$ref: "#/components/schemas/LivePresentationPeriod"
clock:
$ref: "#/components/schemas/LivePresentationClock"
summary:
$ref: "#/components/schemas/LivePresentationSummary"
sourceCursors:
type: object
additionalProperties:
type: string
observedAt:
type: string
format: date-time
validUntil:
type: string
format: date-time
revision:
type: integer
format: int64
LivePresentationCompetitor
type: object
additionalProperties: false
required:
- key
- name
properties:
key:
type: string
name:
type: string
score:
type: number
LivePresentationPeriod
type: object
additionalProperties: false
required:
- key
properties:
key:
type: string
label:
type: string
participant1Score:
type: number
participant2Score:
type: number
updatedAt:
type: string
format: date-time
LivePresentationClock
type: object
additionalProperties: false
properties:
currentPeriod:
type: string
currentTime:
type: string
remainingTime:
type: string
remainingTimeInPeriod:
type: string
stopped:
type: boolean
LivePresentationSummary
type: object
additionalProperties: false
properties:
text:
type: string
CanonicalMarketFamily
type: object
additionalProperties: false
required:
- id
- event_id
- slug
- title
- description
- family_type_key
- family_code
- selection_mode
- membership_complete
- status
- display_order
- revision
- updated_at
- canonical_url
properties:
id:
type: string
event_id:
type: string
slug:
type: string
title:
type: string
description:
type: string
family_type_key:
type: string
family_code:
type: string
selection_mode:
type: string
resolution_cardinality:
type: string
resolution_coordination:
type: string
resolution_constraint_key:
type: string
membership_complete:
type: boolean
status:
type: string
display_order:
type: integer
close_at:
type: string
format: date-time
resolve_at:
type: string
format: date-time
resolved_selection_key:
type: string
revision:
type: integer
format: int64
updated_at:
type: string
format: date-time
canonical_url:
type: string
publication_state:
$ref: "#/components/schemas/MarketPublicationState"
presentation_renderer:
$ref: "#/components/schemas/MarketPresentationRenderer"
live_data_policy:
$ref: "#/components/schemas/MarketLiveDataPolicy"
live:
$ref: "#/components/schemas/LivePresentationSnapshot"
markets:
type: array
items:
$ref: "#/components/schemas/CanonicalMarket"
MarketPublicationState
type: string
enum:
- HIDDEN_BLOCKED
- PUBLIC_BLOCKED
- PUBLIC_OPEN
- PUBLIC_DEGRADED
- RETIRED
MarketPresentationRenderer
type: string
enum:
- TEAM_GAME
- TENNIS_MATCH
- SELECTION_GRID
MarketLiveDataPolicy
type: string
enum:
- OPTIONAL_DISPLAY
- REQUIRED_FOR_IN_PLAY
- NOT_APPLICABLE
CanonicalMarket
type: object
additionalProperties: false
required:
- id
- family_id
- event_id
- title
- description
- status
- market_generation
- settlement_currency
- selection_key
- outcome_id
- outcome_name
- sort_order
- revision
- updated_at
- canonical_url
properties:
id:
type: string
family_id:
type: string
event_id:
type: string
title:
type: string
description:
type: string
status:
type: string
market_generation:
type: integer
format: int64
minimum: 0
description: Exact current admission fence for canonical order entry. A zero
value means this public market is not available through the
generation-fenced canonical order contract; refresh the market after any
stale_market_generation response.
settlement_currency:
type: string
selection_key:
type: string
outcome_id:
type: string
outcome_name:
type: string
sort_order:
type: integer
close_at:
type: string
format: date-time
resolve_at:
type: string
format: date-time
resolved_outcome:
type: string
revision:
type: integer
format: int64
updated_at:
type: string
format: date-time
canonical_url:
type: string
publication_state:
$ref: "#/components/schemas/MarketPublicationState"
presentation_renderer:
$ref: "#/components/schemas/MarketPresentationRenderer"
live_data_policy:
$ref: "#/components/schemas/MarketLiveDataPolicy"
live:
$ref: "#/components/schemas/LivePresentationSnapshot"
external_mappings:
type: array
items:
$ref: "#/components/schemas/CanonicalExternalMapping"
CanonicalExternalMapping
type: object
additionalProperties: false
description: Typed provider identity. Omitted unless `external_mappings` is
explicitly requested.
required:
- provider
- namespace
- role
properties:
provider:
type: string
namespace:
type: string
role:
type: string
provider_event_id:
type: string
provider_event_slug:
type: string
provider_market_id:
type: string
provider_outcome_id:
type: string
condition_id:
type: string
question_id:
type: string
selected_token_id:
type: string
opposite_token_id:
type: string
CanonicalEventResponse
type: object
additionalProperties: false
required:
- event
- alias_used
- source
properties:
event:
$ref: "#/components/schemas/CanonicalEvent"
alias_used:
type: boolean
source:
type: string
enum:
- canonical_catalog
CanonicalFamilyListResponse
type: object
additionalProperties: false
required:
- event_id
- market_families
- count
- source
properties:
event_id:
type: string
market_families:
type: array
items:
$ref: "#/components/schemas/CanonicalMarketFamily"
count:
type: integer
source:
type: string
enum:
- canonical_catalog
MarketFamilyListResponse
type: object
required:
- families
- count
- cached
- source
properties:
families:
type: array
items:
$ref: "#/components/schemas/MarketFamily"
count:
type: integer
cached:
type: boolean
source:
type: string
MarketFamily
type: object
required:
- id
- title
- description
- category
- status
- outcomes
- totalVolume
- volume24h
- liquidity
- endDate
properties:
id:
type: string
eventId:
type: string
eventTitle:
type: string
eventStatus:
type: string
verticalKey:
type: string
pathPatternId:
type: string
providerName:
type: string
providerEventRef:
type: string
classificationStatus:
type: string
classificationSource:
type: string
title:
type: string
description:
type: string
category:
type: string
status:
type: string
enum:
- active
- resolved
- pending
familyStatus:
type: string
familyTypeKey:
type: string
familyCode:
type: string
selectionMode:
type: string
displayOrder:
type: integer
canonicalSlug:
type: string
canonicalUrl:
type: string
publicationState:
$ref: "#/components/schemas/MarketPublicationState"
presentationRenderer:
$ref: "#/components/schemas/MarketPresentationRenderer"
liveDataPolicy:
$ref: "#/components/schemas/MarketLiveDataPolicy"
liveDetails:
$ref: "#/components/schemas/LivePresentationSnapshot"
outcomes:
type: array
items:
$ref: "#/components/schemas/MarketFamilyOutcome"
totalVolume:
type: number
volume24h:
type: number
liquidity:
type: number
endDate:
type: string
imageUrl:
type: string
MarketFamilyOutcome
type: object
required:
- id
- market_id
- market_generation
- name
- probability
- volume24h
- change24h
properties:
id:
type: string
market_id:
type: string
market_generation:
type: integer
format: int64
minimum: 0
description: Current activation generation for this tradable leaf.
publicationState:
$ref: "#/components/schemas/MarketPublicationState"
name:
type: string
probability:
type: number
volume24h:
type: number
change24h:
type: number
flag:
type: string
imageUrl:
type: string
entityId:
type: string
entitySlug:
type: string
entityKind:
type: string
enum:
- team
- athlete
- competition
- venue
entitySportKey:
type: string
shortName:
type: string
countryCode:
type: string
iconMetadata:
type: object
additionalProperties: true
resolved:
type: boolean
result:
type: string
enum:
- win
- lose
- void
CanonicalDirectFamilyListResponse
type: object
additionalProperties: false
required:
- market_families
- count
- limit
- has_more
- next_cursor
- source
properties:
market_families:
type: array
items:
$ref: "#/components/schemas/CanonicalMarketFamily"
count:
type: integer
limit:
type: integer
has_more:
type: boolean
next_cursor:
type: string
source:
type: string
enum:
- canonical_catalog
MarketFamilyResponse
type: object
required:
- family
- source
properties:
family:
$ref: "#/components/schemas/MarketFamily"
source:
type: string
CanonicalFamilyResponse
type: object
additionalProperties: false
required:
- market_family
- alias_used
- source
properties:
market_family:
$ref: "#/components/schemas/CanonicalMarketFamily"
alias_used:
type: boolean
source:
type: string
enum:
- canonical_catalog
LegacyMarketListResponse
type: object
required:
- markets
- count
- cached
- source
properties:
markets:
type: array
items:
$ref: "#/components/schemas/LegacyMarket"
count:
type: integer
cached:
type: boolean
source:
type: string
LegacyMarket
type: object
required:
- market_id
- market_generation
- title
- description
- status
- settlement_currency
- updated_at
- metadata
properties:
market_id:
type: string
market_generation:
type: integer
format: int64
minimum: 0
description: Current activation generation used to fence order commands. A zero
value means the market is not activation-managed and cannot be submitted
through the canonical order path.
title:
type: string
description:
type: string
status:
type: string
enum:
- DRAFT
- OPEN
- SUSPENDED
- RESOLVED
- CLOSED
settlement_currency:
type: string
close_at:
type: string
format: date-time
nullable: true
resolve_at:
type: string
format: date-time
nullable: true
resolved_outcome:
type: string
nullable: true
publication_state:
$ref: "#/components/schemas/MarketPublicationState"
presentation_renderer:
$ref: "#/components/schemas/MarketPresentationRenderer"
live_data_policy:
$ref: "#/components/schemas/MarketLiveDataPolicy"
liveDetails:
$ref: "#/components/schemas/LivePresentationSnapshot"
updated_at:
type: string
format: date-time
metadata:
type: object
additionalProperties: true
best_bid:
type: number
best_ask:
type: number
last_price:
type: number
volume_24h:
type: number
total_volume:
type: number
open_interest:
type: number
seq:
type: integer
CanonicalMarketListResponse
type: object
additionalProperties: false
required:
- markets
- count
- limit
- has_more
- next_cursor
- source
properties:
markets:
type: array
items:
$ref: "#/components/schemas/CanonicalMarket"
count:
type: integer
limit:
type: integer
has_more:
type: boolean
next_cursor:
type: string
source:
type: string
enum:
- canonical_catalog
CanonicalMarketResponse
type: object
additionalProperties: false
required:
- market
- source
properties:
market:
$ref: "#/components/schemas/CanonicalMarket"
source:
type: string
enum:
- canonical_catalog
BookSnapshot
type: object
required:
- market_id
- depth
- bids
- asks
- seq
- updated_at
properties:
market_id:
type: string
depth:
type: integer
minimum: 1
maximum: 200
bids:
type: array
items:
$ref: "#/components/schemas/BookLevel"
asks:
type: array
items:
$ref: "#/components/schemas/BookLevel"
seq:
type: integer
format: int64
description: YM projection sequence; not a durable professional replay cursor.
updated_at:
type: string
format: date-time
description: YM projection timestamp, not a venue-origin timestamp.
BookLevel
type: object
required:
- price
- qty
properties:
price:
type: number
description: Decimal price retained for consumer compatibility.
price_cents:
type: integer
minimum: 0
maximum: 100
description: Exact integer-cent price used by the canonical trading contract
when present.
qty:
type: integer
minimum: 0
CanonicalOrderListResponse
type: object
additionalProperties: false
required:
- schema_version
- count
- orders
- next_cursor
properties:
schema_version:
type: integer
const: 2
count:
type: integer
minimum: 0
orders:
type: array
items:
$ref: "#/components/schemas/Order"
next_cursor:
type: string
description: Empty when no later page exists.
Order
type: object
additionalProperties: false
required:
- id
- created_by_command_id
- market_id
- market_generation
- action
- outcome
- price_cents
- original_quantity
- total_quantity
- cumulative_filled_quantity
- remaining_unfilled_quantity
- working_quantity
- filled_notional_cents
- execution_status
- pending_action
- state_quality
- order_version
- is_terminal
- is_working
- may_still_execute
- has_reserved_exposure
- admitted_at
- updated_at
properties:
post_only:
type: boolean
default: false
description: True when this order was admitted as native maker-or-cancel.
Absence means false.
id:
type: string
format: uuid
created_by_command_id:
type: string
format: uuid
client_order_id:
type: string
market_id:
type: string
market_generation:
type: integer
format: int64
minimum: 1
description: Exact positive market-generation admission fence.
action:
type: string
enum:
- BUY
- SELL
description: Immutable participant action against outcome; SELL is
position-reducing only.
outcome:
type: string
enum:
- YES
- NO
description: Immutable economic outcome. Price and filled notional fields use
this outcome's coordinate.
price_cents:
type: integer
format: int64
minimum: 0
maximum: 100
description: Effective limit price of the named outcome, in cents.
original_quantity:
type: integer
format: int64
minimum: 1
total_quantity:
type: integer
format: int64
minimum: 1
description: Latest venue-effective total quantity; original_quantity remains
the immutable NEW size.
cumulative_filled_quantity:
type: integer
format: int64
minimum: 0
remaining_unfilled_quantity:
type: integer
format: int64
minimum: 0
working_quantity:
type: integer
format: int64
minimum: 0
filled_notional_cents:
type: integer
format: int64
minimum: 0
description: Sum of fill price times quantity in the named outcome coordinate.
execution_status:
type: string
enum:
- PENDING
- WORKING
- PARTIALLY_FILLED
- FILLED
- CANCELLED
- EXPIRED
- REJECTED
pending_action:
type:
- string
- "null"
enum:
- REPLACE
- CANCEL
- null
state_quality:
type: string
enum:
- CONFIRMED
- RECONCILIATION_REQUIRED
order_version:
type: integer
format: int64
minimum: 1
is_terminal:
type: boolean
is_working:
type: boolean
may_still_execute:
type: boolean
has_reserved_exposure:
type: boolean
time_in_force:
type: string
enum:
- GTC
- IOC
- DAY
admitted_at:
type: string
format: date-time
venue_accepted_at:
type:
- string
- "null"
format: date-time
last_fill_at:
type:
- string
- "null"
format: date-time
terminal_at:
type:
- string
- "null"
format: date-time
terminal_reason:
type: string
updated_at:
type: string
format: date-time
CanonicalOrderError
type: object
required:
- code
- message
- retryable
properties:
code:
type: string
message:
type: string
error:
type: string
deprecated: true
description: Transitional alias for message.
command_id:
type:
- string
- "null"
format: uuid
client_request_id:
type:
- string
- "null"
admission_status:
type: string
enum:
- ADMITTED
- REJECTED
description: Present when the response recovered or persisted a canonical command.
outcome_status:
type: string
enum:
- PENDING
- EFFECTIVE
- REJECTED
- NOOP
- CANCELLED_BEFORE_DISPATCH
- UNKNOWN
description: Present when the response recovered or persisted a canonical command.
idempotent_replay:
type: boolean
description: Present when a canonical command identity was recovered.
retryable:
type: boolean
details:
type: object
additionalProperties: true
CreateOrderRequest
allOf:
- $ref: "#/components/schemas/CreateOrderFields"
unevaluatedProperties: false
CreateOrderFields
type: object
required:
- client_order_id
- market_id
- market_generation
- action
- outcome
- price_cents
- quantity
- time_in_force
properties:
client_order_id:
type: string
maxLength: 200
description: Participant-defined order identity, unique within the authenticated
account.
market_id:
type: string
market_generation:
type: integer
format: int64
minimum: 1
description: Exact admission generation fence obtained from the market resource.
action:
type: string
enum:
- BUY
- SELL
description: BUY opens or increases a fully collateralized position in outcome.
SELL reduces that outcome only and is rejected when unreserved holdings
are insufficient; it never reverses or nets another outcome.
outcome:
type: string
enum:
- YES
- NO
description: Immutable economic outcome for this order. All public prices and
fill notionals use this outcome's coordinate.
price_cents:
type: integer
format: int64
minimum: 0
maximum: 100
description: Limit price of outcome, in integer cents. For NO, YM converts once
to the complementary YES-book price internally; clients must not
complement it.
quantity:
type: integer
format: int64
minimum: 1
time_in_force:
type: string
enum:
- GTC
- IOC
- DAY
description: GTC is the professional resting primitive; IOC attempts immediate
execution; DAY is the venue-supported session-scoped alternative.
post_only:
type: boolean
default: false
description: Native maker-or-cancel instruction; requires GTC or DAY. Immutable
through REPLACE. Execution outcome is asynchronous, not implied by HTTP
admission.
example:
client_order_id: strategy-no-000001
market_id: example-market
market_generation: 7
action: BUY
outcome: NO
price_cents: 35
quantity: 10
time_in_force: GTC
CanonicalCommandOrderResponse
type: object
additionalProperties: false
required:
- schema_version
- command
- idempotent_replay
properties:
schema_version:
type: integer
const: 2
command:
$ref: "#/components/schemas/CanonicalOrderCommand"
order:
$ref: "#/components/schemas/Order"
idempotent_replay:
type: boolean
CanonicalOrderCommand
type: object
additionalProperties: false
required:
- command_id
- command_type
- client_request_id
- admission_status
- outcome_status
- created_at
- updated_at
properties:
command_id:
type: string
format: uuid
order_id:
type: string
format: uuid
command_sequence:
type: integer
format: int64
minimum: 1
command_type:
type: string
enum:
- NEW
- REPLACE
- CANCEL
client_request_id:
type: string
client_order_id:
type: string
admission_status:
type: string
enum:
- ADMITTED
- REJECTED
outcome_status:
type: string
enum:
- PENDING
- EFFECTIVE
- REJECTED
- NOOP
- CANCELLED_BEFORE_DISPATCH
- UNKNOWN
reason_code:
type: string
reason_detail:
type: string
admitted_at:
type: string
format: date-time
dispatched_at:
type: string
format: date-time
completed_at:
type: string
format: date-time
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
ReplaceOrderRequest
type: object
additionalProperties: false
required:
- expected_order_version
- price_cents
- total_quantity
properties:
expected_order_version:
type: integer
format: int64
minimum: 1
description: Optimistic concurrency fence from the latest canonical order snapshot.
price_cents:
type: integer
format: int64
minimum: 0
maximum: 100
description: Replacement price in the order's immutable outcome coordinate.
total_quantity:
type: integer
format: int64
minimum: 1
description: Target total quantity including any quantity already filled.
CreateOrderBatchItem
allOf:
- $ref: "#/components/schemas/CreateOrderFields"
- type: object
required:
- client_request_id
properties:
client_request_id:
type: string
minLength: 1
maxLength: 200
pattern: ^(?![Yy][Mm]:).+$
description: Per-item participant-scoped command identity.
unevaluatedProperties: false
OrderBatchResponse
type: object
required:
- accepted
- failed
- results
properties:
accepted:
type: integer
failed:
type: integer
results:
type: array
items:
type: object
required:
- index
- accepted
- retryable
- status
properties:
index:
type: integer
accepted:
type: boolean
retryable:
type: boolean
status:
type: integer
client_request_id:
type: string
order_id:
type: string
error:
type: string
body:
type: object
additionalProperties: true
CancellationStatus
type: object
additionalProperties: false
required:
- request_id
- requested_at
- total_orders
- remaining_orders
- status
properties:
request_id:
type: string
format: uuid
requested_at:
type: string
format: date-time
market_id:
type: string
total_orders:
type: integer
minimum: 0
maximum: 10000
remaining_orders:
type: integer
minimum: 0
status:
type: string
enum:
- PENDING
- COMPLETE
CancellationLease
type: object
required:
- lease_id
- state
- timeout_seconds
- expires_at
properties:
lease_id:
type: string
format: uuid
state:
type: string
enum:
- ARMED
- FIRED
- DISARMED
timeout_seconds:
type: integer
minimum: 10
maximum: 120
expires_at:
type: string
format: date-time
cancellation_ids:
type: array
items:
type: string
format: uuid
description: Immutable groups captured atomically at expiry. Poll every group's
cancellation status; FIRED alone is not proof of completion.
PostedExecution
type: object
required:
- fill_id
- trade_id
- order_id
- market_id
- action
- outcome
- price_cents
- quantity
- liquidity_role
- fee_cents
- fee_policy_id
- ledger_entry_id
- posted_at
properties:
fill_id:
type: string
description: Stable posting-side fill ID; string to avoid 64-bit JSON precision loss
trade_id:
type: string
format: uuid
order_id:
type: string
format: uuid
client_order_id:
type: string
market_id:
type: string
action:
type: string
enum:
- BUY
- SELL
outcome:
type: string
enum:
- YES
- NO
price_cents:
type: integer
minimum: 0
maximum: 100
quantity:
type: integer
liquidity_role:
type: string
enum:
- MAKER
- TAKER
fee_cents:
type: integer
fee_policy_id:
type: integer
description: The paired trade's taker fee policy; zero means no fee receipt policy
ledger_entry_id:
type: string
format: uuid
posted_at:
type: string
format: date-time
description: Database posting time
not exchange transaction time: null
ProfessionalCommandFrame
type: object
additionalProperties: false
required:
- request_id
- operation
- client_request_id
properties:
request_id:
type: string
minLength: 1
maxLength: 200
operation:
type: string
enum:
- NEW
- REPLACE
- CANCEL
- CANCEL_ALL
client_request_id:
type: string
minLength: 1
maxLength: 200
order_id:
type: string
format: uuid
body:
type: object
description: NEW body is CreateOrderRequest; REPLACE body is
ReplaceOrderRequest. CANCEL body is absent or empty. CANCEL_ALL body is empty
or has market_id. order_id is required only for REPLACE/CANCEL. request_id is
connection-local; client_request_id is the durable REST Idempotency-Key, not a
new identity.
ProfessionalCommandWelcome
type: object
required:
- type
- contract
- schema_version
- normal_inflight_limit
- cancel_inflight_limit
- heartbeat_ms
- recovery
- execution_feed
properties:
type:
const: stream.welcome
contract:
const: ym.order-entry.v1
schema_version:
const: 2
normal_inflight_limit:
const: 12
cancel_inflight_limit:
const: 4
heartbeat_ms:
const: 5000
recovery:
const: retry_identical_client_request_id
execution_feed:
const: /v1/trading-feed
ProfessionalCommandResponse
type: object
required:
- type
- request_id
- status
- body
properties:
type:
const: command.response
request_id:
type: string
status:
type: integer
minimum: 200
maximum: 599
body:
type: object
description: Unmodified corresponding REST response or transport error;
admission is not execution.
retry_after:
type: string
ProfessionalBookEnvelope
type: object
required:
- stream_id
- sequence
- event
properties:
stream_id:
type: string
format: uuid
sequence:
type: integer
minimum: 1
description: Connection-local contiguous sequence including control messages;
starts at one.
event:
oneOf:
- type: object
required:
- type
- data
properties:
type:
const: book.snapshot
data:
$ref: "#/components/schemas/ProfessionalBookSnapshot"
- type: object
required:
- type
- data
properties:
type:
const: book.delta
data:
$ref: "#/components/schemas/ProfessionalBookDelta"
- type: object
required:
- type
- data
properties:
type:
const: book.changes
data:
$ref: "#/components/schemas/ProfessionalBookChanges"
- type: object
required:
- type
- data
properties:
type:
const: market.status
data:
$ref: "#/components/schemas/ProfessionalBookStatus"
- type: object
required:
- type
- data
properties:
type:
const: stream.welcome
data:
type: object
required:
- contract
- heartbeat_ms
- recovery
- price_coordinate
- markets
properties:
contract:
const: ym.market-data.v1
heartbeat_ms:
const: 5000
recovery:
const: new_connection_snapshot
price_coordinate:
const: YES
markets:
type: array
items:
type: string
- type: object
required:
- type
- data
properties:
type:
const: stream.ready
data:
type: object
required:
- markets
properties:
markets:
type: array
items:
type: string
- type: object
required:
- type
- data
properties:
type:
const: stream.heartbeat
data:
type: object
required:
- at
properties:
at:
type: string
format: date-time
ProfessionalBookSnapshot
allOf:
- $ref: "#/components/schemas/ProfessionalBookStatus"
- type: object
required:
- bids
- asks
- updated_at
properties:
bids:
type: array
items:
$ref: "#/components/schemas/ProfessionalBookLevel"
asks:
type: array
items:
$ref: "#/components/schemas/ProfessionalBookLevel"
updated_at:
type: string
format: date-time
ProfessionalBookStatus
type: object
required:
- market_id
- market_generation
- lifecycle
- state
- feed_epoch
properties:
market_id:
type: string
market_generation:
type: integer
format: int64
lifecycle:
type: string
state:
type: string
enum:
- FRESH
- STALE
- CLOSED
reason:
type: string
feed_epoch:
type: integer
format: int64
ProfessionalBookLevel
type: object
required:
- price
- price_cents
- qty
properties:
price:
type: number
minimum: 0
maximum: 1
price_cents:
type: integer
minimum: 0
maximum: 100
qty:
type: integer
format: int64
minimum: 1
ProfessionalBookDelta
allOf:
- $ref: "#/components/schemas/ProfessionalBookChange"
- type: object
required:
- market_id
- market_generation
- observed_at
properties:
market_id:
type: string
market_generation:
type: integer
format: int64
observed_at:
type: string
format: date-time
ProfessionalBookChange
type: object
required:
- side
- price_cents
- quantity
properties:
side:
type: string
enum:
- BUY
- SELL
price_cents:
type: integer
minimum: 0
maximum: 100
quantity:
type: integer
format: int64
minimum: 0
description: Absolute resulting level quantity; zero removes the level.
ProfessionalBookChanges
type: object
required:
- market_id
- market_generation
- observed_at
- changes
properties:
market_id:
type: string
market_generation:
type: integer
format: int64
observed_at:
type: string
format: date-time
changes:
type: array
minItems: 1
items:
$ref: "#/components/schemas/ProfessionalBookChange"
description: Apply all changes atomically before exposing the resulting book.
PrivateFeedLifecycleEvent
type: object
additionalProperties: false
description: Order lifecycle events carry a complete order snapshot. The sole
orderless variant is order.command_result for an authenticated, schema-valid
NEW command rejected by YM before an order exists; that variant carries the
canonical command and no order, fill, or venue fields.
required:
- schema_version
- cursor
- event_id
- event_type
- account_scope
- cause
- ym_received_at
- ym_projected_at
- server_sent_at
allOf:
- if:
properties:
event_type:
const: order.command_result
required:
- event_type
then:
required:
- command
oneOf:
- required:
- order_id
- order_version
- order
- properties:
command:
allOf:
- $ref: "#/components/schemas/CanonicalOrderCommand"
- type: object
required:
- command_type
- admission_status
- outcome_status
properties:
command_type:
const: NEW
admission_status:
const: REJECTED
outcome_status:
const: REJECTED
not:
anyOf:
- required:
- order_id
- required:
- order_version
- required:
- order
- required:
- fill
- required:
- venue
- required:
- venue_event_at
else:
required:
- order_id
- order_version
- order
not:
required:
- command
- if:
properties:
event_type:
const: order.fill
required:
- event_type
then:
required:
- fill
else:
not:
required:
- fill
properties:
schema_version:
type: integer
const: 2
cursor:
type: string
description: Persist only after the client fully validates and applies this event.
event_id:
type: string
format: uuid
event_type:
type: string
enum:
- order.admitted
- order.working
- order.replaced
- order.fill
- order.cancelled
- order.expired
- order.rejected
- order.command_result
account_scope:
type: string
const: self
order_id:
type: string
format: uuid
order_version:
type: integer
format: int64
minimum: 1
cause:
$ref: "#/components/schemas/PrivateFeedCause"
venue:
$ref: "#/components/schemas/PrivateFeedVenueEvidence"
venue_event_at:
type: string
format: date-time
ym_received_at:
type: string
format: date-time
ym_projected_at:
type: string
format: date-time
server_sent_at:
type: string
format: date-time
command:
allOf:
- $ref: "#/components/schemas/CanonicalOrderCommand"
description: Present on order.command_result and omitted on other lifecycle
events. A rejected NEW may omit order_id when admission failed before any
order existed.
order:
$ref: "#/components/schemas/Order"
fill:
$ref: "#/components/schemas/PrivateFeedFill"
PrivateFeedCause
type: object
additionalProperties: false
required:
- type
properties:
type:
type: string
command_id:
type: string
format: uuid
client_request_id:
type: string
reason_code:
type: string
PrivateFeedVenueEvidence
type: object
additionalProperties: false
required:
- provider
description: Participant-safe venue identity. Raw DX replay offsets,
session/MPID topology, account identity, and the counterparty-side execution
identifier are never exposed.
properties:
provider:
type: string
example: dxmatch
symbol:
type: string
execution_id:
type: string
description: The authenticated participant's own DX execution identifier.
trade_id:
type: string
description: Shared DX trade identifier when supplied by the Trading Feed.
match_id:
type: string
description: Shared DX match identifier when supplied by the Trading Feed.
PrivateFeedFill
type: object
additionalProperties: false
required:
- trade_id
- market_id
- market_generation
- price_cents
- quantity
- liquidity_role
- settlement_status
properties:
trade_id:
type: string
market_id:
type: string
market_generation:
type: integer
format: int64
price_cents:
type: integer
format: int64
minimum: 0
maximum: 100
description: Participant-relative fill price in the nested order's outcome
coordinate; never the internal YES-book price for a NO order.
quantity:
type: integer
format: int64
liquidity_role:
type: string
enum:
- MAKER
- TAKER
settlement_status:
type: string
enum:
- POSTED
PrivateFeedControlMessage
type: object
additionalProperties: false
required:
- schema_version
- type
- server_sent_at
properties:
schema_version:
type: integer
const: 2
type:
type: string
enum:
- stream.welcome
- stream.snapshot_started
- stream.snapshot_chunk
- stream.snapshot_completed
- stream.ready
- stream.heartbeat
- stream.error
server_sent_at:
type: string
format: date-time
contract:
type: string
const: Private Order & Execution Feed v2
heartbeat_seconds:
type: integer
minimum: 1
max_frame_bytes:
type: integer
minimum: 1
delivery:
type: string
const: at_least_once
snapshot_id:
type: string
format: uuid
as_of_cursor:
type: string
scope:
type: string
const: NONTERMINAL
order_count:
type: integer
minimum: 0
orders:
type: array
maxItems: 100
items:
$ref: "#/components/schemas/Order"
code:
type: string
enum:
- CURSOR_EXPIRED
- REPLAY_LIMIT_EXCEEDED
- INVALID_CURSOR
- SLOW_CONSUMER
- STREAM_ERROR
message:
type: string
last_written_cursor:
type: string