{"services":[{"id":"agora","title":"Agora Auction House API","baseUrls":["https://agora.kairos.trade","https://staging-agora.kairos.trade"],"spec":{"openapi":"3.1.0","info":{"title":"Agora Auction House API","version":"1.0.0-pilot","description":"Private first-price sealed auctions for venue-backed prediction contracts.\nActive auctions are never published through an unauthenticated or global\nfeed. All price and quantity values are integer atoms.\n\n## Authentication\n\nEvery `/v1` operation requires a first-party Kairos session JWT\n(`Authorization: Bearer <jwt>`, RS256, `iss: kairos.trade`,\n`aud: kairos-api`, `ver: 1`, an expiry claim, and a subject enrolled in the\nAuction House). Any other credential shape — missing header, wrong\naudience, revoked session, unenrolled subject — is rejected with `401`\nbefore the handler runs. There is no API-key or anonymous tier. Browser\nWebSocket clients pass the same JWT as the `Sec-WebSocket-Protocol`\nsubprotocol pair `authorization, Bearer.<base64url(jwt)>`.\n\nEvery session is granted the `auction:create` and `auction:quote`\ncapabilities; they only allow the request to reach the fail-closed\nGo-owned economic policy store, which remains the real authorization\nboundary.\n\n## Errors\n\nEvery error body is a flat JSON object: `{\"error\": \"<code>\"}`, plus a\n`\"message\"` field on rejections raised by the auction engine. Codes are\nstable; `message` is descriptive and must not be parsed. `422` rejections\ncarry the durable auction reason code (for example `INVALID_PRICE_TICK`,\n`UNAUTHORIZED_SIZE`, `SERVICE_CAPACITY_EXCEEDED`) as the `error` value.\n\n## Request limits\n\nRequest bodies are capped at 1 MiB (`413 payload_too_large`). JSON is\nparsed strictly: no `null` values, no duplicate object keys, no unknown\nfields, no trailing data, and at most 64 levels of nesting. Owner-routing\nheaders (`X-Service-Token`, `X-Agora-Peer`, `X-Agora-Active-Only`,\n`X-Agora-Owner-Read-Fallback`) are internal capabilities and are rejected\nwith `403` on the public listener.\n\n## Rate limiting\n\nOnly state-changing operations are metered, by a per-account and\nper-auction token bucket owned by the authoritative shard, evaluated\nbefore any risk reservation or journal write. Exceeding a bucket returns\n`429 auction_mutation_rate_limited`. No `Retry-After` or `X-RateLimit-*`\nheaders are emitted; back off and retry with the same idempotency key.\nReads, both preflight calls, and the stream are not metered.\n"},"servers":[{"url":"https://agora.kairos.trade","description":"Production. Agora is allow-listed RFQ and the production host is not currently enabled, so this base URL does not answer public requests yet."},{"url":"https://staging-agora.kairos.trade","description":"Staging."}],"security":[{"bearerAuth":[]}],"paths":{"/healthz":{"get":{"security":[],"summary":"Process health","description":"Liveness of the local authority journal. Touches no dependency and is never rate-limited.","x-kairos-auth":"public","responses":{"200":{"description":"Healthy","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"},"example":{"status":"ok"}}}},"503":{"description":"The local authority journal has failed (`journal_failed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"},"example":{"status":"journal_failed"}}}}}}},"/readyz":{"get":{"security":[],"summary":"Owner readiness","description":"Reports whether this owner accepts new work. Fails closed while\ndraining, while the local authority journal is unavailable, and when\nthe shared Kairos control schema cannot be reached.\n","x-kairos-auth":"public","responses":{"200":{"description":"Ready for new work","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"},"example":{"status":"ready"}}}},"503":{"description":"Draining, local authority journal unavailable, or shared Kairos control schema unavailable. `status` names the failing condition.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}}}}}},"/v1/auctions":{"post":{"summary":"Create and open a private auction","description":"The resolved audience and identity policy freeze before this call\nreturns. Economic identity is derived exclusively from the\nauthenticated principal; a caller-supplied account identifier is\nneither required nor trusted. Supply the idempotency key in the header\nor request body. If both are present, they must match exactly.\n","x-kairos-auth":"session","x-kairos-scope":"auction:create","x-kairos-bucket":"create","x-kairos-rate-limit":"2/s per account, burst 4","parameters":[{"$ref":"#/components/parameters/OptionalIdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAuction"}}}},"responses":{"200":{"description":"Identical idempotent replay","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuctionView"}}}},"201":{"description":"Durably opened","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuctionView"}}}},"400":{"description":"`invalid_json` (unparseable, `null`, duplicate key, unknown field,\nor over 64 levels deep), `multiple_json_values` (trailing data),\n`idempotency_key_mismatch` (header and body keys differ), or\n`idempotency_key_required` (no key at all, so the create cannot be\nrouted to its owner shard).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"idempotency_key_mismatch"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`forbidden` when the session lacks the `auction:create`\ncapability, or `internal_routing_headers_forbidden` when the\nrequest carries an owner-routing header.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"$ref":"#/components/responses/Conflict"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"422":{"$ref":"#/components/responses/Rejected"},"429":{"$ref":"#/components/responses/MutationRateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"description":"New creates are paused: `projection_backlog` (asynchronous\nprojection backlog is unsafe), `clock_unhealthy`,\n`journal_capacity_unhealthy`, or — when the owning shard for this\nidempotency key cannot be reached — `auction_owner_unavailable`,\n`owner_routing_unavailable`, or `owner_response_invalid`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List only auctions visible to the authenticated principal","description":"Merges the live owner shards with the shared completed projection into\none page ordered by open time descending, then auction ID descending.\nUnknown or repeated query parameters are rejected rather than ignored.\n","x-kairos-auth":"session","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":50,"default":50}},{"name":"cursor","in":"query","description":"Opaque continuation returned by the previous page.","schema":{"type":"string","maxLength":512}}],"responses":{"200":{"description":"Role-filtered private auction page.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuctionPage"}}}},"400":{"description":"`invalid_list_page` — unparseable query string, an unknown or repeated parameter, a `limit` outside 1..50, or a cursor that is over 512 characters, not base64url, over 256 decoded bytes, or missing its open time or auction ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"invalid_list_page"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/InternalRoutingHeaders"},"503":{"description":"`completed_projection_unavailable` when the shared completed\nprojection cannot be read, or `auction_list_projection_invalid` /\n`auction_list_item_exceeds_response_limit` when a retained owner\nview cannot be encoded within the 2 MiB response bound.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/auctions/preflight":{"post":{"summary":"Verify originator capacity without reserving it","description":"Returns a point-in-time, read-only capacity verdict for the\nauthenticated originator across every requested route. The response\nexposes bounded executable quantity, never raw collateral, position,\nvenue-account, credential, or control-group data. No auction, journal\nevent, or risk reservation is created. POST /v1/auctions always repeats\nthe same capacity calculation and reserves atomically, so a successful\npreflight is evidence rather than a guarantee against concurrent use.\nThis call is not rate-limited and ignores the idempotency key.\n","x-kairos-auth":"session","x-kairos-scope":"auction:create","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAuction"}}}},"responses":{"200":{"description":"Current originator route-capacity evidence","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OriginatorPreflight"}}}},"400":{"$ref":"#/components/responses/InvalidJSON"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`forbidden` when the session lacks the `auction:create`\ncapability, or `internal_routing_headers_forbidden` when the\nrequest carries an owner-routing header.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"422":{"$ref":"#/components/responses/Rejected"},"500":{"description":"`internal_error` — this owner cannot verify authority safely\n(draining, unhealthy clock or journal capacity, saturated active\nauction cache), or the admission authority returned evidence that\nfailed its own invariants. No 503 is emitted on this path.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"internal_error","message":"auction request could not be completed"}}}}}}},"/v1/auctions/{auctionID}":{"parameters":[{"$ref":"#/components/parameters/AuctionID"}],"get":{"summary":"Get a role-filtered private auction view","description":"Reads the live owner first and falls back to the shared completed\nprojection. Existence of a private auction is opaque to unrelated\nprincipals: they receive the same 404 as a caller asking for an ID\nthat was never issued.\n","x-kairos-auth":"session","responses":{"200":{"description":"Originator or invited-MM view","headers":{"X-Agora-Completed-As-Of":{"description":"Present only when the view was served from the shared completed projection; RFC 3339 nanosecond projection time.","schema":{"type":"string","format":"date-time"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuctionView"}}}},"400":{"$ref":"#/components/responses/InvalidAuctionID"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/InternalRoutingHeaders"},"404":{"description":"Auction absent or not visible to the caller; private-auction existence is opaque"},"503":{"description":"`completed_projection_unavailable` when the shared projection\ncannot be read, or `auction_owner_unavailable` /\n`owner_routing_unavailable` / `owner_response_invalid` when the\nowning shard for this auction cannot be reached.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/auctions/{auctionID}/events":{"parameters":[{"$ref":"#/components/parameters/AuctionID"}],"get":{"summary":"Get the caller's complete role-filtered audit timeline","description":"Reads the existing owner journal for a live auction or the existing\nshared kairos_execution projection for completed history. Completeness\nis verified before returning. Raw authority sequence numbers, event\nIDs, owner/session identifiers, hidden execution identities, and every\ncompeting-maker quote event are omitted so gaps cannot disclose private\nparticipation.\n","x-kairos-auth":"session","responses":{"200":{"description":"Complete ordered timeline visible to this principal","headers":{"Cache-Control":{"schema":{"type":"string","example":"private, no-store"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuctionAuditTimeline"}}}},"400":{"$ref":"#/components/responses/InvalidAuctionID"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/InternalRoutingHeaders"},"404":{"description":"Auction absent or not visible to the caller; private-auction existence is opaque"},"500":{"description":"`internal_error` — the projected history was readable but not contiguous through its authoritative snapshot, so no partial timeline is returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The complete owner journal or shared audit projection cannot\ncurrently be verified: `audit_history_unavailable` (owner journal\nscan failed) or `audit_projection_unavailable` (no audit reader,\nor the shared projection backlog is unsafe).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/auctions/{auctionID}/cancel":{"parameters":[{"$ref":"#/components/parameters/AuctionID"},{"$ref":"#/components/parameters/IdempotencyKey"}],"post":{"summary":"Cancel an open auction","description":"Originator-only. The request body is optional; omit it entirely to\ncancel with reason code `UNSPECIFIED`. When a body is sent, an\n`idempotency_key` inside it must match the header exactly.\n","x-kairos-auth":"session","x-kairos-scope":"auction:create","x-kairos-bucket":"cancel","x-kairos-rate-limit":"5/s per auction (burst 10) and 20/s per account (burst 40)","requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelAuctionRequest"}}}},"responses":{"200":{"description":"Durably cancelled or duplicate","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuctionMutationResult"}}}},"400":{"description":"`invalid_json`, `multiple_json_values`, `idempotency_key_mismatch`, or `invalid_auction_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`forbidden` when a delivered invitee knows the auction but is not\nits originator, or when the session lacks the `auction:create`\ncapability; `internal_routing_headers_forbidden` when the request\ncarries an owner-routing header.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Auction absent or not visible to the caller"},"409":{"$ref":"#/components/responses/Conflict"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"422":{"description":"`auction_rejected` — the auction is no longer open (close already\nwon the owner sequence race), the `Idempotency-Key` header is\nmissing or over 200 bytes, the reason code is unknown, or\n`operator_note` is malformed, over 240 bytes, or absent while\n`reason_code` is `OTHER`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"auction_rejected","message":"auction is not open"}}}},"429":{"$ref":"#/components/responses/MutationRateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/OwnerUnavailable"}}}},"/v1/auctions/{auctionID}/quotes":{"parameters":[{"$ref":"#/components/parameters/AuctionID"}],"post":{"summary":"Submit or revise one firm executable price and quantity","description":"Invited makers only. Supply the idempotency key in the header or\nrequest body. If both are present, they must match exactly. Revisions\nare strictly sequential: the first quote must be revision 1 and each\nlater quote exactly one higher than the caller's current revision.\n","x-kairos-auth":"session","x-kairos-scope":"auction:quote","x-kairos-bucket":"quote","x-kairos-rate-limit":"25/s per auction (burst 50) and 100/s per account (burst 200)","parameters":[{"$ref":"#/components/parameters/OptionalIdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitQuote"}}}},"responses":{"200":{"description":"Identical idempotent replay","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Quote"}}}},"201":{"description":"Quote journaled and accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Quote"}}}},"400":{"description":"`invalid_json`, `multiple_json_values`, `idempotency_key_mismatch`, or `invalid_auction_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`forbidden` when the session lacks the `auction:quote` capability,\nor `internal_routing_headers_forbidden` when the request carries\nan owner-routing header.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Auction absent or not visible to the caller"},"409":{"$ref":"#/components/responses/Conflict"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"422":{"description":"Durably journaled bid rejection. `error` is the reason code:\n`INVALID_PRICE_RANGE`, `INVALID_PRICE_TICK`,\n`INVALID_QUANTITY_TICK`, `BID_INVALID`, `UNAUTHORIZED_SIZE` (over\nthe frozen approved size), `LATE_BID` (the auction closed), or a\nreason returned by the reservation authority. A missing or\noversized idempotency key is also rejected here as\n`auction_rejected`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"INVALID_PRICE_TICK","message":"INVALID_PRICE_TICK"}}}},"429":{"$ref":"#/components/responses/MutationRateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/OwnerUnavailable"}}},"delete":{"summary":"Withdraw the caller's active quote","x-kairos-auth":"session","x-kairos-scope":"auction:quote","x-kairos-bucket":"withdraw","x-kairos-rate-limit":"25/s per auction (burst 50) and 100/s per account (burst 200)","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"description":"Quote durably withdrawn or duplicate","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteMutationResult"}}}},"400":{"$ref":"#/components/responses/InvalidAuctionID"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`forbidden` when the session lacks the `auction:quote` capability,\nor `internal_routing_headers_forbidden` when the request carries\nan owner-routing header.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Auction or caller's active quote absent; unrelated callers cannot distinguish them"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"description":"`auction_rejected` — the auction is no longer open, or the `Idempotency-Key` header is missing or over 200 bytes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"auction_rejected","message":"auction is not open"}}}},"429":{"$ref":"#/components/responses/MutationRateLimited"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/OwnerUnavailable"}}}},"/v1/auctions/{auctionID}/quotes/preflight":{"parameters":[{"$ref":"#/components/parameters/AuctionID"}],"post":{"summary":"Verify the invited maker's price-specific quote capacity","description":"Returns only the authenticated invitee's bounded capacity at the\nproposed price across every frozen route. It exposes no raw portfolio,\nother invitee, competing quote, venue-account, or control-group data\nand creates no reservation or journal event. Quote submission repeats\nthe same calculation inside its serializable reservation transaction.\nThis call is not rate-limited and takes no idempotency key.\n","x-kairos-auth":"session","x-kairos-scope":"auction:quote","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuotePreflightRequest"}}}},"responses":{"200":{"description":"Current invitee quote-capacity evidence","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuotePreflight"}}}},"400":{"description":"`invalid_json`, `multiple_json_values`, or `invalid_auction_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`forbidden` when the session lacks the `auction:quote` capability,\nor `internal_routing_headers_forbidden` when the request carries\nan owner-routing header.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Auction absent or caller was not delivered an invitation"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"422":{"description":"`auction_rejected` when the auction is no longer open or the\nauction reference is unbounded, otherwise the durable quote reason\ncode (`INVALID_PRICE_RANGE`, `INVALID_PRICE_TICK`,\n`INVALID_QUANTITY_TICK`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"auction_rejected","message":"auction is not open"}}}},"500":{"description":"`internal_error` — the admission authority returned capacity evidence that failed its own invariants. No 503 is emitted on this path.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/OwnerUnavailable"}}}},"/v1/stream":{"get":{"summary":"Private WebSocket event stream","description":"Each principal receives only auctions it originated or was invited to,\nwith the same field filtering as GET. There is no active public stream.\nBrowser clients authenticate with subprotocols `authorization` and\n`Bearer.<base64url(jwt)>`; polling is only a reconnect fallback.\n\nThe server sends a `SNAPSHOT` frame, then one `SYNC_COMPLETE` frame\nonce every owner shard has replied, then an `UPDATE` frame per visible\nevent. The session is re-verified every 15 seconds and the connection\nis bounded by the token's expiry: a revoked, rotated, or expired\nsession is closed with code 1008 `session reauthentication required`.\nLoss of a peer owner stream closes with 1011 `auction owner stream\nunavailable`.\n","x-kairos-auth":"session","responses":{"101":{"description":"Switching Protocols"},"400":{"description":"The request is not a valid WebSocket upgrade. The body is plain text","not the JSON error envelope.":null},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`websocket_origin_forbidden` when `Origin` is absent, `null`, or\nnot an exact match for an allowed origin, or\n`internal_routing_headers_forbidden` when the request carries an\nowner-routing header.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"websocket_origin_forbidden"}}}}}}},"/v1/invitations":{"get":{"summary":"Read the authenticated participant's durable private invitation inbox","description":"Returns only unexpired invitations published to the caller's account before the common close time.","x-kairos-auth":"session","responses":{"200":{"description":"Unexpired private invitation envelopes","content":{"application/json":{"schema":{"type":"object","required":["invitations"],"properties":{"invitations":{"type":"array","items":{"$ref":"#/components/schemas/InvitationInboxItem"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/InternalRoutingHeaders"},"503":{"description":"`invitation_inbox_unavailable` — the inbox is not configured on this deployment or its durable read failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"invitation_inbox_unavailable"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}},"parameters":{"AuctionID":{"name":"auctionID","in":"path","required":true,"description":"Canonical UUID whose first character is the owner shard (0, 1, or 2).","schema":{"type":"string","format":"uuid"}},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":1,"maxLength":200}},"OptionalIdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Required here or in the JSON body; both values must match when supplied together.","schema":{"type":"string","minLength":1,"maxLength":200}}},"responses":{"Unauthorized":{"description":"The bearer session is absent, malformed, expired, revoked, signed by\nan unrecognized key, carries the wrong issuer, audience, or schema\nversion, or its subject is not enrolled in the Auction House.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"unauthorized","message":"authentication required"}}}},"InternalRoutingHeaders":{"description":"`internal_routing_headers_forbidden` — the request carried `X-Service-Token`, `X-Agora-Peer`, `X-Agora-Active-Only`, or `X-Agora-Owner-Read-Fallback`. These are internal owner-routing capabilities, not public API options.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"internal_routing_headers_forbidden"}}}},"InvalidJSON":{"description":"`invalid_json` (unparseable, `null`, duplicate key, unknown field, or\nover 64 levels deep) or `multiple_json_values` (trailing data after\nthe request object).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"invalid_json","message":"request body is not valid JSON"}}}},"InvalidAuctionID":{"description":"`invalid_auction_id` — the path identifier is not a canonical UUID carrying a valid owner shard.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"invalid_auction_id"}}}},"PayloadTooLarge":{"description":"`payload_too_large` — the request body exceeded 1 MiB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"payload_too_large"}}}},"Conflict":{"description":"`IDEMPOTENCY_CONFLICT` when the idempotency key was already used with\na different request, or `REVISION_CONFLICT` when a quote revision is\nnot exactly one higher than the caller's current revision.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"IDEMPOTENCY_CONFLICT","message":"IDEMPOTENCY_CONFLICT"}}}},"Rejected":{"description":"Admission, tick, reserve, audience, or state rejection. `error` is the\ndurable auction reason code — for example `INVALID_REQUEST`,\n`TTL_OUT_OF_RANGE`, `BELOW_MIN_BLOCK_SIZE`, `NO_ELIGIBLE_MMS`,\n`PARTICIPANT_INELIGIBLE`, `SERVICE_CAPACITY_EXCEEDED`,\n`ATOMICITY_UNAVAILABLE` — or `auction_rejected` for an untyped\nrequest-shape failure.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"TTL_OUT_OF_RANGE","message":"TTL_OUT_OF_RANGE"}}}},"MutationRateLimited":{"description":"`auction_mutation_rate_limited` — the per-account or per-auction\nmutation bucket is exhausted, or the durable event budget reserved for\nauction lifecycle work has been reached. No `Retry-After` or\n`X-RateLimit-*` header is emitted. Retrying with the same idempotency\nkey is safe.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"auction_mutation_rate_limited","message":"auction mutation rate limit exceeded"}}}},"InternalError":{"description":"`internal_error` — the owner is draining, its journal is unavailable,\ninvitation publication for an idempotent replay is still pending, or\nan untyped dependency failure occurred. `encode_create`,\n`encode_quote`, and `encode_quote_preflight` indicate the request\ncould not be re-encoded for owner routing.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"internal_error","message":"auction request could not be completed"}}}},"OwnerUnavailable":{"description":"The owning shard for this auction could not be reached or answered\nunusably: `auction_owner_unavailable`, `owner_routing_unavailable`, or\n`owner_response_invalid`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"auction_owner_unavailable"}}}}},"schemas":{"Error":{"type":"object","required":["error"],"description":"Flat error envelope used by every JSON error response. `message` is\npresent on rejections raised by the auction engine and absent on\ntransport-level rejections such as `invalid_list_page` or\n`payload_too_large`. Match on `error`; never parse `message`.\n","properties":{"error":{"type":"string","description":"Stable machine-readable code","or the durable auction reason code on a 422/409 rejection.":null},"message":{"type":"string","description":"Human-readable detail; not a stable contract."}}},"Status":{"type":"object","required":["status"],"properties":{"status":{"type":"string"}}},"InvitationInboxItem":{"type":"object","required":["auction_id","invitation","close_time","published_at"],"properties":{"auction_id":{"type":"string","format":"uuid"},"invitation":{"$ref":"#/components/schemas/InvitationPayload"},"close_time":{"type":"string","format":"date-time"},"published_at":{"type":"string","format":"date-time"}}},"InvitationPayload":{"type":"object","required":["auction_id","market_id","outcome_id","side","fill_instruction","min_fill_quantity_atoms","max_fill_quantity_atoms","execution_objective_quantity_atoms","objective_mode","close_time","venue_scope","customer_class","allocation_policy","price_tick_atoms","quantity_tick_atoms","minimum_leg_atoms","maximum_mm_concentration_bps","private_fee_profile"],"properties":{"auction_id":{"type":"string","format":"uuid"},"market_id":{"type":"string"},"outcome_id":{"type":"string"},"side":{"enum":["BUY","SELL"]},"fill_instruction":{"enum":["FOK","FLEXIBLE"]},"min_fill_quantity_atoms":{"type":"integer","format":"int64"},"target_fill_quantity_atoms":{"type":"integer","format":"int64"},"max_fill_quantity_atoms":{"type":"integer","format":"int64"},"execution_objective_quantity_atoms":{"type":"integer","format":"int64"},"objective_mode":{"enum":["STOP_AT_TARGET","SEEK_MAXIMUM"]},"close_time":{"type":"string","format":"date-time"},"venue_scope":{"type":"array","items":{"type":"string"}},"customer_class":{"type":"string"},"allocation_policy":{"enum":["PRICE_FIRST","COMPLETION_FIRST"]},"price_tick_atoms":{"type":"integer","format":"int64"},"quantity_tick_atoms":{"type":"integer","format":"int64"},"minimum_leg_atoms":{"type":"integer","format":"int64"},"maximum_mm_concentration_bps":{"type":"integer","format":"int64"},"private_fee_profile":{"$ref":"#/components/schemas/FeeProfile"},"originator_identity":{"type":"string","description":"Present only when the auction's identity policy is REVEAL_TO_INVITEES."}}},"CreateAuction":{"type":"object","required":["market_id","outcome_id","side","fill_instruction","hard_reserve_price_atoms","slippage_reference_policy","max_cumulative_slippage_bps","venue_scope","audience_mode","identity_disclosure_mode","allocation_priority_policy","max_total_execution_time_ms"],"properties":{"idempotency_key":{"type":"string","description":"May be supplied instead of the header"},"market_id":{"type":"string","maxLength":128},"outcome_id":{"type":"string","maxLength":128},"side":{"enum":["BUY","SELL"]},"fill_instruction":{"enum":["FOK","FLEXIBLE"]},"fixed_quantity_atoms":{"type":"integer","format":"int64","minimum":1},"flexible_fill":{"$ref":"#/components/schemas/FlexibleFill"},"hard_reserve_price_atoms":{"$ref":"#/components/schemas/PriceAtoms"},"slippage_reference_price_atoms":{"allOf":[{"$ref":"#/components/schemas/PriceAtoms"}],"description":"Required only when slippage_reference_policy is APPROVED_EXPLICIT."},"slippage_reference_policy":{"enum":["PUBLIC_EXECUTABLE_VWAP","APPROVED_EXPLICIT"]},"max_cumulative_slippage_bps":{"type":"integer","format":"int64","minimum":0,"maximum":10000},"minimum_private_improvement_bps":{"type":"integer","format":"int64","minimum":0,"maximum":10000},"maximum_mm_concentration_bps":{"type":"integer","format":"int64","minimum":1,"maximum":10000,"default":10000},"minimum_mm_reliability_tier":{"enum":["UNRATED","C","B","A"],"default":"UNRATED"},"allocation_priority_policy":{"enum":["PRICE_FIRST","COMPLETION_FIRST"],"description":"Frozen before invitations; price-first minimizes marginal economics while completion-first may prefer one firm able to complete the target."},"auction_ttl_ms":{"type":"integer","format":"int64","minimum":2000,"maximum":2592000000,"default":5000,"description":"Custom sealed bidding window from 2 seconds through 30 days. Omit or send 0 to accept the 5-second default."},"venue_scope":{"type":"array","minItems":1,"maxItems":8,"items":{"type":"string","maxLength":64}},"audience_mode":{"enum":["MANUAL","SAVED_GROUP","RECOMMENDED"]},"allowed_participants":{"type":"array","maxItems":16,"items":{"type":"string","maxLength":128}},"saved_group_id":{"type":"string","maxLength":128},"excluded_participants":{"type":"array","maxItems":16,"items":{"type":"string","maxLength":128}},"max_recipients":{"type":"integer","minimum":1,"maximum":16,"default":16},"identity_disclosure_mode":{"enum":["CLASS_ONLY","REVEAL_TO_INVITEES","REVEAL_ON_AWARD"]},"fallback_ladder":{"type":"array","maxItems":4,"items":{"$ref":"#/components/schemas/FallbackRung"}},"max_total_execution_time_ms":{"type":"integer","format":"int64","minimum":1,"maximum":120000},"client_metadata":{"type":"object","additionalProperties":{"type":"string"},"description":"Bounded non-authoritative display metadata, at most 32 entries and 4 KiB total. market_title and market_provider provide the catalog label. Optional client_order_id is a 1–64 character desk reconciliation reference using letters, numbers, or . _ : / -; it is originator-only and should also derive the create idempotency key."}}},"FlexibleFill":{"type":"object","required":["min_fill_quantity_atoms","target_fill_quantity_atoms","max_fill_quantity_atoms","objective_mode"],"properties":{"min_fill_quantity_atoms":{"type":"integer","format":"int64","minimum":1},"target_fill_quantity_atoms":{"type":"integer","format":"int64","minimum":1},"max_fill_quantity_atoms":{"type":"integer","format":"int64","minimum":1},"objective_mode":{"enum":["STOP_AT_TARGET","SEEK_MAXIMUM"]}},"description":"Minimum, target, and maximum are always explicit; SEEK_MAXIMUM may continue beyond the target but never beyond maximum."},"OriginatorPreflight":{"type":"object","required":["status","requested_quantity_atoms","maximum_executable_quantity_atoms","checked_at","valid_until","routes"],"properties":{"status":{"enum":["EXECUTABLE","INSUFFICIENT_CAPACITY"]},"requested_quantity_atoms":{"type":"string","pattern":"^[1-9]\\d*$","description":"Requested maximum quantity encoded exactly."},"maximum_executable_quantity_atoms":{"type":"string","pattern":"^(0|[1-9]\\d*)$","description":"Minimum currently executable quantity across all requested routes."},"checked_at":{"type":"string","format":"date-time"},"valid_until":{"type":"string","format":"date-time","description":"Earliest risk","mapping":null,"rule":null,"or fee authority expiry across the route set.":null},"routes":{"type":"array","minItems":1,"maxItems":5,"items":{"$ref":"#/components/schemas/OriginatorRouteCapacity"}}}},"OriginatorRouteCapacity":{"type":"object","required":["venue","rails","resource_type","maximum_executable_quantity_atoms","risk_source_sequence","risk_valid_until","authority_valid_until"],"properties":{"venue":{"type":"string","maxLength":64},"rails":{"type":"array","minItems":1,"maxItems":3,"uniqueItems":true,"items":{"enum":["PRIVATE_BLOCK","VENUE_NATIVE_RFQ","CLOB"]}},"resource_type":{"enum":["COLLATERAL","POSITION"]},"maximum_executable_quantity_atoms":{"type":"string","pattern":"^(0|[1-9]\\d*)$"},"risk_source_sequence":{"type":"string","pattern":"^[1-9]\\d*$","description":"Opaque venue-account authority sequence."},"risk_valid_until":{"type":"string","format":"date-time"},"authority_valid_until":{"type":"string","format":"date-time","description":"Earliest mapping","route-rule":null,"fee":null,"or execution-deadline expiry for this route.":null}}},"QuotePreflightRequest":{"type":"object","required":["price_atoms","max_executable_quantity_atoms"],"additionalProperties":false,"properties":{"price_atoms":{"$ref":"#/components/schemas/PriceAtoms"},"max_executable_quantity_atoms":{"type":"integer","format":"int64","minimum":1}}},"QuotePreflight":{"type":"object","required":["status","requested_quantity_atoms","maximum_executable_quantity_atoms","checked_at","valid_until","routes"],"properties":{"status":{"enum":["EXECUTABLE","INSUFFICIENT_CAPACITY"]},"requested_quantity_atoms":{"type":"string","pattern":"^[1-9]\\d*$"},"maximum_executable_quantity_atoms":{"type":"string","pattern":"^(0|[1-9]\\d*)$","description":"Minimum price-specific capacity across all frozen routes."},"checked_at":{"type":"string","format":"date-time"},"valid_until":{"type":"string","format":"date-time"},"routes":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/QuoteRouteCapacity"}}}},"QuoteRouteCapacity":{"type":"object","required":["venue","resource_type","maximum_executable_quantity_atoms","risk_source_sequence","risk_valid_until","authority_valid_until"],"properties":{"venue":{"type":"string","maxLength":64},"resource_type":{"enum":["COLLATERAL","POSITION"]},"maximum_executable_quantity_atoms":{"type":"string","pattern":"^(0|[1-9]\\d*)$"},"risk_source_sequence":{"type":"string","pattern":"^[1-9]\\d*$"},"risk_valid_until":{"type":"string","format":"date-time"},"authority_valid_until":{"type":"string","format":"date-time","description":"Earliest frozen mapping","route-rule":null,"fee":null,"reservation":null,"or execution-deadline expiry for this route.":null}}},"FallbackRung":{"type":"object","required":["action"],"properties":{"action":{"enum":["EXECUTE","CANCEL_REMAINDER"],"default":"EXECUTE"},"rail":{"enum":["VENUE_NATIVE_RFQ","CLOB"]},"venue":{"type":"string"},"limit_price_atoms":{"$ref":"#/components/schemas/PriceAtoms"},"minimum_quantity_atoms":{"type":"integer","format":"int64","minimum":1},"maximum_quantity_atoms":{"type":"integer","format":"int64","minimum":1},"maximum_fraction_bps":{"type":"integer","format":"int64","minimum":1,"maximum":10000,"default":10000},"timeout_ms":{"type":"integer","format":"int64","minimum":1,"default":2000},"maximum_quote_age_ms":{"type":"integer","format":"int64","minimum":1,"maximum":5000,"default":500},"allow_partial":{"type":"boolean"},"on_unavailable":{"enum":["CONTINUE","STOP"],"default":"CONTINUE"},"on_reject":{"enum":["CONTINUE","STOP"],"default":"CONTINUE"},"fee_profile":{"$ref":"#/components/schemas/FeeProfile","description":"Server-pinned; caller values are not authoritative."},"route_rule_profile":{"$ref":"#/components/schemas/RouteRuleProfile","description":"Server-pinned executable venue and mapping contract; caller values are not authoritative."}}},"SubmitQuote":{"type":"object","required":["revision","price_atoms","max_executable_quantity_atoms"],"additionalProperties":false,"properties":{"idempotency_key":{"type":"string","description":"May be supplied instead of the header"},"revision":{"type":"integer","format":"int64","minimum":1},"price_atoms":{"$ref":"#/components/schemas/PriceAtoms"},"max_executable_quantity_atoms":{"type":"integer","format":"int64","minimum":1}}},"PriceAtoms":{"type":"integer","format":"int64","minimum":0,"maximum":10000,"description":"One atom is 0.01 cent of probability-dollar price."},"Quote":{"type":"object","required":["quote_id","auction_id","revision","price_atoms","max_executable_quantity_atoms","receive_sequence","received_at","status"],"properties":{"quote_id":{"type":"string","format":"uuid"},"auction_id":{"type":"string","format":"uuid"},"revision":{"type":"string","pattern":"^(0|[1-9]\\d*)$","description":"Opaque unsigned quote revision; compare as a decimal string."},"price_atoms":{"$ref":"#/components/schemas/PriceAtoms"},"max_executable_quantity_atoms":{"type":"integer","format":"int64"},"receive_sequence":{"type":"string","pattern":"^(0|[1-9]\\d*)$","description":"Participant-scoped durable acknowledgement sequence encoded as an opaque decimal string; private FIFO coordinates are never exposed."},"received_at":{"type":"string","format":"date-time"},"status":{"enum":["ACTIVE","WITHDRAWN","SUPERSEDED","FROZEN","SELECTED","INELIGIBLE"]}}},"QuoteMutationResult":{"type":"object","required":["quote","duplicate"],"properties":{"quote":{"$ref":"#/components/schemas/Quote"},"duplicate":{"type":"boolean","description":"True when the identical durable withdrawal already existed."}}},"AuctionMutationResult":{"type":"object","required":["auction","duplicate"],"properties":{"auction":{"$ref":"#/components/schemas/AuctionView"},"duplicate":{"type":"boolean","description":"True when the identical durable cancellation already existed."}}},"CancelAuctionRequest":{"type":"object","additionalProperties":false,"description":"Optional. Omitting the body cancels with reason_code UNSPECIFIED.","properties":{"idempotency_key":{"type":"string","description":"Must equal the Idempotency-Key header when supplied."},"reason_code":{"enum":["UNSPECIFIED","CLIENT_REQUEST","RISK_REDUCTION","MARKET_VIEW_CHANGED","ROUTING_CHANGE","DUPLICATE_ORDER","OTHER"],"default":"UNSPECIFIED","description":"Originator-private durable desk reason for cancelling the live auction."},"operator_note":{"type":"string","maxLength":240,"description":"Optional bounded originator-private audit note; required when reason_code is OTHER."}}},"AuctionView":{"type":"object","required":["auction"],"description":"Fields are filtered by originator/invitee role and identity policy.","properties":{"auction":{"$ref":"#/components/schemas/Auction"},"originator_identity":{"type":"string","description":"Policy-authorized public program selector; never a program, execution-account, venue-account, or credential identifier."},"audience_participant_ids":{"type":"array","description":"Originator-only policy-authorized public counterparty selectors; hidden execution identities never cross this boundary.","items":{"type":"string"}},"own_quote":{"$ref":"#/components/schemas/Quote"},"allocations":{"type":"array","items":{"$ref":"#/components/schemas/Allocation"}}}},"AuctionAuditTimeline":{"type":"object","required":["auction_id","as_of","complete","events"],"properties":{"auction_id":{"type":"string","format":"uuid"},"as_of":{"type":"string","format":"date-time"},"complete":{"type":"boolean","enum":[true],"description":"True only after the selected durable history is verified contiguous through its authoritative snapshot."},"events":{"type":"array","maxItems":4096,"description":"Ordered caller-visible events. Hidden events do not leave sequence gaps because raw authority coordinates are never exposed.","items":{"$ref":"#/components/schemas/AuctionAuditEvent"}}}},"AuctionAuditEvent":{"type":"object","required":["type","occurred_at"],"properties":{"type":{"type":"string"},"occurred_at":{"type":"string","format":"date-time"},"state_before":{"type":"string"},"state_after":{"type":"string"},"reason":{"type":"string"},"actor_scope":{"enum":["SYSTEM","YOU","COUNTERPARTY"]},"quote_revision":{"type":"string","pattern":"^(0|[1-9]\\d*)$","description":"Caller-owned quote revision only."},"quote_price_atoms":{"$ref":"#/components/schemas/PriceAtoms"},"quote_quantity_atoms":{"type":"integer","format":"int64","minimum":1,"description":"Caller-owned quote quantity only."},"cancellation_reason_code":{"enum":["UNSPECIFIED","CLIENT_REQUEST","RISK_REDUCTION","MARKET_VIEW_CHANGED","ROUTING_CHANGE","DUPLICATE_ORDER","OTHER"],"description":"Originator-only durable cancellation instruction; never returned to invitees."},"operator_note":{"type":"string","maxLength":240,"description":"Originator-only cancellation note."}}},"AuctionPage":{"type":"object","required":["auctions","completed_as_of","active_owners_complete"],"properties":{"auctions":{"type":"array","items":{"$ref":"#/components/schemas/AuctionView"}},"next_cursor":{"type":"string","description":"Opaque cursor for the next older page; absent at the end."},"completed_as_of":{"type":"string","format":"date-time","description":"Projection time of the newest completed entry on this page; the zero time when the page contains no completed auction."},"active_owners_complete":{"type":"boolean","description":"False when one or more live owner shards could not answer this page."}}},"Auction":{"type":"object","description":"Role-filtered auction state. Reserve, slippage, fallback, and public\nbenchmark fields are present only for the originator.\n","properties":{"auction_id":{"type":"string","format":"uuid"},"owner_epoch":{"type":"string","pattern":"^(0|[1-9]\\d*)$","description":"Opaque unsigned owner-generation counter; compare as a decimal string."},"customer_class":{"type":"string"},"market_id":{"type":"string"},"outcome_id":{"type":"string"},"side":{"enum":["BUY","SELL"]},"fill_instruction":{"enum":["FOK","FLEXIBLE"]},"min_fill_quantity_atoms":{"type":"integer","format":"int64"},"target_fill_quantity_atoms":{"type":"integer","format":"int64"},"max_fill_quantity_atoms":{"type":"integer","format":"int64"},"objective_mode":{"enum":["STOP_AT_TARGET","SEEK_MAXIMUM"]},"allocation_priority_policy":{"enum":["PRICE_FIRST","COMPLETION_FIRST"]},"execution_objective_quantity_atoms":{"type":"integer","format":"int64"},"filled_quantity_atoms":{"type":"integer","format":"int64"},"hard_reserve_price_atoms":{"$ref":"#/components/schemas/PriceAtoms"},"slippage_reference_price_atoms":{"$ref":"#/components/schemas/PriceAtoms"},"slippage_reference_policy":{"enum":["PUBLIC_EXECUTABLE_VWAP","APPROVED_EXPLICIT"]},"max_cumulative_slippage_bps":{"type":"integer","format":"int64","minimum":0,"maximum":10000},"minimum_private_improvement_bps":{"type":"integer","format":"int64","minimum":0,"maximum":10000},"maximum_mm_concentration_bps":{"type":"integer","format":"int64","minimum":1,"maximum":10000},"minimum_mm_reliability_tier":{"enum":["UNRATED","C","B","A"]},"public_benchmark_price_atoms":{"$ref":"#/components/schemas/PriceAtoms"},"public_benchmark_quantity_atoms":{"type":"integer","format":"int64","minimum":1},"public_benchmark_observed_at":{"type":"string","format":"date-time"},"public_benchmark_source":{"type":"string"},"benchmark_calculation_version":{"type":"string"},"max_total_execution_time_ms":{"type":"integer","format":"int64","minimum":1},"venue_scope":{"type":"array","items":{"type":"string"}},"audience_mode":{"enum":["MANUAL","SAVED_GROUP","RECOMMENDED"]},"max_recipients":{"type":"integer","minimum":1,"maximum":16},"identity_disclosure_mode":{"enum":["CLASS_ONLY","REVEAL_TO_INVITEES","REVEAL_ON_AWARD"]},"fallback_ladder":{"type":"array","items":{"$ref":"#/components/schemas/FallbackRung"}},"rule_profile":{"$ref":"#/components/schemas/RuleProfile"},"client_metadata":{"type":"object","additionalProperties":{"type":"string"},"description":"Catalog labels are participant-visible. The optional client_order_id is returned only to the originator for OMS reconciliation."},"invitation_delivery":{"allOf":[{"$ref":"#/components/schemas/InvitationDelivery"}],"description":"Originator-only durable delivery summary."},"state":{"enum":["PENDING_INVITATIONS","OPEN","CLOSING","EXECUTING","RECONCILING","PROCESSING","FILLED","PARTIALLY_FILLED","UNFILLED","CANCELLED","FAILED","COMPLETED"],"description":"PROCESSING is an invitee-only privacy projection used after close while another participant's execution or reconciliation state remains hidden."},"final_reason":{"enum":["OBJECTIVE_REACHED","TARGET_NOT_REACHED","MAXIMUM_NOT_REACHED","INSUFFICIENT_SIZE","MIN_FILL_NOT_REACHABLE","NO_BIDS","RESERVE_NOT_MET","MIN_BLOCK_SIZE_NOT_MET","REFERENCE_UNAVAILABLE","NO_PRIVATE_IMPROVEMENT","NO_ELIGIBLE_QUOTES","USER_CANCELLED","AUCTION_OWNER_LOST","MINIMUM_TRANCHE_BROKEN","BROKEN_FOK","EXECUTION_UNKNOWN","RECONCILIATION_REQUIRED","LADDER_EXHAUSTED","CANCEL_REMAINDER","RUNG_UNAVAILABLE","RUNG_QUOTE_STALE","RUNG_MINIMUM_NOT_REACHED","RUNG_SLIPPAGE_BREACH","VENUE_REJECTED","EXECUTION_REJECTED","EXECUTION_TIMEOUT","WINNER_EXPIRED","ATOMICITY_UNAVAILABLE","VENUE_UNAVAILABLE","VENUE_NATIVE_RFQ_NO_RESPONSE","VENUE_NATIVE_RFQ_RESERVE_BREACH","VENUE_NATIVE_RFQ_SLIPPAGE_BREACH","VENUE_NATIVE_RFQ_FAILED","CLOB_NO_LIQUIDITY","CLOB_RESERVE_BREACH","CLOB_SLIPPAGE_BREACH","CLOB_FALLBACK_FAILED","MAPPING_INVALIDATED","COLLATERAL_INVALIDATED","PARTICIPANT_INELIGIBLE","SELF_TRADE_PREVENTED","UNAUTHORIZED_SIZE","SIGNER_UNAVAILABLE","MARKET_HALTED","VENUE_SESSION_LOST","MM_POPULATION_UNAVAILABLE","AUCTION_EXPIRED","TICK_PROFILE_STALE","VENUE_RULE_CHANGED","UNSUPPORTED_RAIL","INVALID_FALLBACK_POLICY","TOTAL_DEADLINE_EXCEEDED","INTERNAL_ERROR","BID_INVALID","INVALID_PRICE_RANGE","INVALID_PRICE_TICK","INVALID_QUANTITY_TICK","REVISION_CONFLICT","LATE_BID"]},"cancellation_reason_code":{"enum":["UNSPECIFIED","CLIENT_REQUEST","RISK_REDUCTION","MARKET_VIEW_CHANGED","ROUTING_CHANGE","DUPLICATE_ORDER","OTHER"],"description":"Originator-only durable cancellation instruction."},"cancellation_note":{"type":"string","maxLength":240,"description":"Originator-only bounded operator audit note."},"reason_history":{"type":"array","items":{"$ref":"#/components/schemas/ReasonRecord"}},"minimum_tranche_established":{"type":"boolean"},"allocation_calculation_version":{"type":"string"},"capacity_authority_status":{"enum":["PENDING_VENUE_RECONCILIATION","PENDING_PORTFOLIO_COVERAGE","PORTFOLIO_COVERAGE_CONFIRMED","AUTHORITY_EVIDENCE_UNAVAILABLE"],"description":"Fail-closed venue and portfolio-capacity reconciliation state."},"capacity_covered_by_source_sequence":{"type":"string","pattern":"^(0|[1-9][0-9]*)$","description":"Originator-only sealed portfolio source sequence that explicitly covered every confirmed execution token."},"capacity_covered_at":{"type":"string","format":"date-time","description":"Originator-only time at which explicit sealed coverage retired the capacity fence."},"capacity_portfolio_watermark":{"type":"string","maxLength":256,"description":"Originator-only opaque portfolio revision certified by the covering snapshot."},"open_time":{"type":"string","format":"date-time"},"close_time":{"type":"string","format":"date-time"},"finalized_at":{"type":"string","format":"date-time"},"sequence":{"type":"string","pattern":"^(0|[1-9]\\d*)$","description":"Opaque auction authority sequence; compare as a decimal string."}}},"InvitationDelivery":{"type":"object","required":["requested_count","delivered_count","by_transport","participants"],"properties":{"requested_count":{"type":"integer","minimum":0,"maximum":16},"delivered_count":{"type":"integer","minimum":0,"maximum":16},"by_transport":{"type":"object","additionalProperties":false,"required":["BROWSER","FIX"],"properties":{"BROWSER":{"type":"integer","minimum":0,"maximum":16},"FIX":{"type":"integer","minimum":0,"maximum":16}}},"last_delivered_at":{"type":"string","format":"date-time"},"participants":{"type":"array","maxItems":16,"description":"Originator-only delivery evidence keyed by public counterparty selector; hidden execution and delivery-session identities are never exposed.","items":{"$ref":"#/components/schemas/InvitationParticipantDelivery"}}}},"InvitationParticipantDelivery":{"type":"object","required":["participant_id","delivered"],"properties":{"participant_id":{"type":"string","maxLength":128,"description":"Public counterparty selector frozen for this auction."},"delivered":{"type":"boolean"},"transport":{"enum":["BROWSER","FIX"]},"delivered_at":{"type":"string","format":"date-time"}}},"RuleProfile":{"type":"object","description":"Immutable venue, tick, atomicity, freshness, and fee-assumption snapshot pinned at admission.","required":["version","venue","market_class","private_rail","price_tick_atoms","quantity_tick_atoms","minimum_block_atoms","minimum_leg_atoms","maximum_allocation_atoms","atomic_private_execution","public_benchmark_fee_scale_ppm","public_benchmark_max_age_ms","benchmark_calculation_version","valid_until"],"properties":{"version":{"type":"string"},"venue":{"type":"string"},"market_class":{"type":"string"},"private_rail":{"enum":["PRIVATE_BLOCK","VENUE_NATIVE_RFQ","CLOB"]},"price_tick_atoms":{"type":"integer","format":"int64","minimum":1},"quantity_tick_atoms":{"type":"integer","format":"int64","minimum":1},"minimum_block_atoms":{"type":"integer","format":"int64","minimum":0},"minimum_leg_atoms":{"type":"integer","format":"int64","minimum":0},"maximum_allocation_atoms":{"type":"integer","format":"int64","minimum":1},"atomic_private_execution":{"type":"boolean"},"private_fee_profile":{"$ref":"#/components/schemas/FeeProfile"},"public_benchmark_fee_scale_ppm":{"type":"integer","format":"int64","minimum":0,"maximum":1000000},"public_benchmark_max_age_ms":{"type":"integer","format":"int64","minimum":1,"maximum":5000},"benchmark_calculation_version":{"type":"string"},"minimum_scorecard_sample_count":{"type":"integer","minimum":1,"default":10},"maximum_scorecard_age_ms":{"type":"integer","format":"int64","minimum":1},"valid_until":{"type":"string","format":"date-time"}}},"FeeProfile":{"type":"object","required":["fee_profile_id","fee_profile_version","model","rate_ppm","fixed_fee_atoms","minimum_fee_atoms","prediction_fee_scale_ppm","rounding_mode","valid_until"],"properties":{"fee_profile_id":{"type":"string"},"fee_profile_version":{"type":"string"},"model":{"enum":["NONE","NOTIONAL_PPM","PREDICTION_MARKET"],"default":"NONE"},"rate_ppm":{"type":"integer","format":"int64","minimum":-1000000,"maximum":1000000},"fixed_fee_atoms":{"type":"integer","format":"int64"},"minimum_fee_atoms":{"type":"integer","format":"int64","minimum":0},"prediction_fee_scale_ppm":{"type":"integer","format":"int64","minimum":-1000000,"maximum":1000000},"rounding_mode":{"enum":["AGAINST_ORIGINATOR","NEAREST"],"default":"AGAINST_ORIGINATOR"},"valid_until":{"type":"string","format":"date-time"}}},"RouteRuleProfile":{"type":"object","required":["version","venue","rail","mapping_version","venue_market_id","venue_outcome_id","route_id","price_tick_atoms","quantity_tick_atoms","minimum_order_atoms","maximum_order_atoms","atomic","self_trade_prevention_mode","control_group_version","valid_from","valid_until","mapping_valid_from","mapping_valid_until"],"properties":{"version":{"type":"string"},"venue":{"type":"string"},"rail":{"enum":["VENUE_NATIVE_RFQ","CLOB"]},"mapping_version":{"type":"string"},"venue_market_id":{"type":"string"},"venue_outcome_id":{"type":"string"},"route_id":{"type":"string"},"price_tick_atoms":{"type":"integer","format":"int64","minimum":1},"quantity_tick_atoms":{"type":"integer","format":"int64","minimum":1},"minimum_order_atoms":{"type":"integer","format":"int64","minimum":1},"maximum_order_atoms":{"type":"integer","format":"int64","minimum":1},"atomic":{"type":"boolean"},"self_trade_prevention_mode":{"enum":["CONTROL_GROUP_REJECT"]},"control_group_version":{"type":"string"},"valid_from":{"type":"string","format":"date-time"},"valid_until":{"type":"string","format":"date-time"},"mapping_valid_from":{"type":"string","format":"date-time"},"mapping_valid_until":{"type":"string","format":"date-time"}}},"ReasonRecord":{"type":"object","required":["reason","stage","occurred_at","contributing"],"properties":{"reason":{"type":"string"},"stage":{"type":"string"},"rung_index":{"type":"integer","minimum":0},"occurred_at":{"type":"string","format":"date-time"},"contributing":{"type":"boolean"}}},"Allocation":{"type":"object","required":["allocation_id","price_atoms","quantity_atoms","rail","venue","minimum_tranche","execution_status"],"properties":{"allocation_id":{"type":"string","format":"uuid"},"participant_id":{"type":"string","description":"Policy-authorized public counterparty selector when visible to this principal; never a hidden execution or venue-account identifier."},"price_atoms":{"$ref":"#/components/schemas/PriceAtoms"},"quantity_atoms":{"type":"integer","format":"int64"},"rail":{"enum":["PRIVATE_BLOCK","VENUE_NATIVE_RFQ","CLOB"]},"venue":{"type":"string"},"minimum_tranche":{"type":"boolean"},"execution_status":{"enum":["PLANNED","SUBMITTED","CONFIRMED","REJECTED","UNKNOWN"]},"venue_execution_id":{"type":"string"},"confirmed_quantity_atoms":{"type":"integer","format":"int64"},"confirmed_price_atoms":{"$ref":"#/components/schemas/PriceAtoms"},"expected_fee_atoms":{"type":"string","pattern":"^(0|-?[1-9][0-9]*)$"},"expected_all_in_value_atoms":{"type":"string","pattern":"^(0|-?[1-9][0-9]*)$"},"confirmed_fee_atoms":{"type":"string","pattern":"^(0|-?[1-9][0-9]*)$"},"confirmed_all_in_value_atoms":{"type":"string","pattern":"^(0|-?[1-9][0-9]*)$"},"rung_index":{"type":"integer","minimum":0},"submitted_at":{"type":"string","format":"date-time"},"acknowledged_at":{"type":"string","format":"date-time"},"finalized_at":{"type":"string","format":"date-time"}}}}}}},{"id":"data-api","title":"Kairos Data API","baseUrls":["https://data.kairos.trade","https://staging-data.kairos.trade"],"spec":{"openapi":"3.1.0","info":{"title":"Kairos Data API","version":"1.0.0","summary":"The full Kairos data plane — markets, candles, trades, search, discover, sports, trader analytics, and PnL.","description":"The Kairos Data API at `data.kairos.trade` is the platform's primary data\nplane: market metadata and prices, OHLCV candles, live venue trade proxies,\nnormalized trade history, full-text search, discovery feeds, the sports\ncatalog, public trader analytics, and account PnL.\n\n## Authentication\n\nMost endpoints accept a Kairos **API key** (`X-Client-Id` + `X-Api-Key` +\n`X-Api-Secret`, all three) or a first-party session JWT. Endpoints tagged\n`public` (trader analytics, provider configs, several sports feeds) need\nno credentials at all. Some endpoints additionally require an API-key\n**scope** (`trade:read`, `position:read`) — stated per operation.\n\nFor high-volume market-data consumption (candles/trades/metadata at\nscale), prefer the dedicated **Market Data API** at `md.kairos.trade` —\nit has higher budgets, ETag caching, and a binary candle format.\n\n## Rate limits\n\nRate limits use a sliding window keyed on the authenticated user (JWT\n`sub`) or, for anonymous callers, the trusted client IP. Routes belong to\na named **bucket** (`x-kairos-bucket`) whose per-minute budget is shared\nby every route in it; routes with no bucket run under the service default\nof 100/minute. `x-kairos-rate-limit` states the bucket's compile-time\ndefault — operators can raise or lower a bucket at runtime, so treat the\ndocumented number as the baseline, not a contract. 429 responses carry\n`Retry-After` and `X-RateLimit-*`.\n\n## Conventions\n\n- Prices are on the **0–100 cents scale** unless a field says otherwise\n  (trader-analytics position/trade prices use the 0–1 scale; each field's\n  description states its scale).\n- Errors: `{\"detail\": \"<message>\"}` for handler errors; FastAPI's\n  standard validation envelope for 422s. Errors surfaced from a venue\n  adapter instead use `{\"error\", \"provider\", \"operation\", \"message\"}` —\n  see `DataProviderError`. Unhandled failures always return the generic\n  500 body; internal details are logged, never returned.\n- Every request body is capped at 8 MiB service-wide (413).\n","contact":{"name":"Kairos","url":"https://app.kairos.trade/docs/api-reference"},"termsOfService":"https://kairos.trade/terms"},"servers":[{"url":"https://data.kairos.trade","description":"Production"},{"url":"https://staging-data.kairos.trade","description":"Staging"}],"security":[{"apiKeyClientId":[],"apiKeyKey":[],"apiKeySecret":[]}],"components":{"securitySchemes":{"apiKeyClientId":{"type":"apiKey","in":"header","name":"X-Client-Id","description":"Credential client id (`kairos_ck_...`). Must be sent together with X-Api-Key and X-Api-Secret."},"apiKeyKey":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"64-char hex API key."},"apiKeySecret":{"type":"apiKey","in":"header","name":"X-Api-Secret","description":"64-char hex API secret."},"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"First-party Kairos session JWT (web app sessions). Not issued to API consumers."}},"schemas":{"DataError":{"type":"object","description":"Handler error envelope (FastAPI HTTPException).","required":["detail"],"properties":{"detail":{"type":"string","description":"Human-readable error message.","example":"Invalid provider"}}},"DataProviderError":{"type":"object","description":"Adapter-layer error envelope. Returned instead of `DataError` whenever the failure comes from\na venue adapter rather than the handler: 400 (validation), 401 (venue auth), 404 (not found),\n429 (venue rate limit), 502 (provider/parse error), 503 (connection error or open circuit).\n","required":["error","message"],"properties":{"error":{"type":"string","description":"Machine-readable class of failure.","enum":["validation_error","authentication_failed","not_found","rate_limit_exceeded","provider_error","parse_error","service_unavailable"]},"provider":{"type":["string","null"],"description":"Venue the adapter was talking to.","example":"kalshi"},"operation":{"type":["string","null"],"description":"Adapter operation that failed."},"message":{"type":"string","description":"Human-readable error message."}}},"MarketsActivePageResponse":{"type":"object","description":"One page of the active-market listing for a provider, with `provider`/`source` stamped on by the API.","required":["exchange_id","markets","count","next_cursor","has_more","provider","source"],"properties":{"exchange_id":{"type":"string","description":"Lower-cased exchange id the market is listed under.","example":"polymarket"},"markets":{"type":"array","items":{"$ref":"#/components/schemas/MarketsActiveMarket"}},"count":{"type":"integer","description":"Number of markets in this page (`markets.length`).","example":100},"next_cursor":{"type":["string","null"],"description":"Opaque cursor for the next page, or null if this is the last page."},"has_more":{"type":"boolean"},"provider":{"type":"string","description":"Echo of the validated/lowercased `provider` query param.","example":"polymarket"},"source":{"type":"string","description":"Opaque internal origin marker. NOT a stable part of the contract — its value may change without notice; do not depend on or switch on it."}}},"MarketsActiveMarket":{"type":"object","description":"One market's cached metadata, including its full upstream `raw` payload.","properties":{"exchange_id":{"type":"string"},"market_id":{"type":"string"},"condition_id":{"type":"string"},"event_id":{"type":"string"},"title":{"type":"string"},"neg_risk":{"type":"boolean"},"tick_size":{"type":["number","null"]},"taker_base_fee_bps":{"type":["integer","null"]},"fees_enabled":{"type":["boolean","null"]},"category":{"type":["string","null"]},"group_slug":{"type":["string","null"]},"fee_type":{"type":["string","null"]},"status":{"type":["string","null"]},"image":{"type":["string","null"]},"icon":{"type":["string","null"]},"end_date":{"type":["string","null"]},"open_time":{"type":["string","null"]},"outcomes":{"type":"array","items":{"$ref":"#/components/schemas/MarketsActiveMarketOutcome"}},"raw":{"type":"object","additionalProperties":true,"description":"Full upstream provider payload as last captured."},"resolution_status":{"type":"string","description":"Present only once the market has a resolution status recorded."},"payout_numerators":{"type":"array","items":{"type":"integer"},"description":"Present only once CTF payout numerators are known."},"resolved_ts":{"type":"string"},"proposed_price":{"type":"number"},"challenge_window_ends_at":{"type":"string"}}},"MarketsActiveMarketOutcome":{"type":"object","properties":{"outcome":{"type":"string","example":"Yes"},"normalized_outcome":{"type":"string","example":"yes"},"token_id":{"type":"string"},"outcome_index":{"type":"integer","description":"Present only for exchanges keyed by market_id, index < 2."},"side":{"type":"string","enum":[true,"no"],"description":"Present only for exchanges keyed by market_id, index < 2."}}},"MarketsDetailsRequest":{"type":"object","required":["markets"],"properties":{"markets":{"type":"array","items":{"$ref":"#/components/schemas/MarketsDetailsRequestItem"}}}},"MarketsDetailsRequestItem":{"type":"object","required":["market_id","provider_id"],"properties":{"market_id":{"type":"string","description":"Matched against market_id OR condition_id OR token_id."},"provider_id":{"type":"integer"}}},"MarketsDetailsResponse":{"type":"object","required":["markets"],"properties":{"markets":{"type":"array","items":{"$ref":"#/components/schemas/MarketsDetail"}}}},"MarketsDetail":{"type":"object","required":["market_id","provider_id","event_id","name","category","status","condition_id","token_id","token_ids","outcomes"],"properties":{"market_id":{"type":"string"},"provider_id":{"type":"integer"},"event_id":{"type":["string","null"]},"name":{"type":["string","null"]},"category":{"type":["string","null"]},"status":{"type":["string","null"]},"condition_id":{"type":["string","null"]},"token_id":{"type":["string","null"]},"token_ids":{"type":"array","items":{"type":"string"},"description":"Full provider token roster, aligned by index with outcomes."},"outcomes":{"type":"array","items":{"type":"string"},"description":"Outcome labels aligned by index with token_ids."}}},"MarketsBatchPricesRequest":{"type":"object","required":["markets"],"properties":{"markets":{"type":"array","maxItems":300,"items":{"$ref":"#/components/schemas/MarketsBatchPricesRequestItem"}}}},"MarketsBatchPricesRequestItem":{"type":"object","required":["market_id","provider_id"],"properties":{"market_id":{"type":"string"},"provider_id":{"type":"integer"}}},"MarketsBatchPricesResponse":{"type":"object","description":"Prices keyed by the requested market_id.","additionalProperties":{"$ref":"#/components/schemas/MarketsPrice"}},"MarketsPrice":{"type":"object","required":["price"],"properties":{"price":{"type":"number","description":"Current venue reference price. Scale is venue-dependent and is not normalized."},"volume":{"type":["string","null"]},"liquidity":{"type":["number","null"]}}},"MarketsTickSizeResponse":{"oneOf":[{"$ref":"#/components/schemas/MarketsTickSizeResolved"},{"$ref":"#/components/schemas/MarketsTickSizeUnsupported"}],"description":"A resolved tick grid (kalshi/polymarket) or predict.fun's explicit unsupported payload — distinguish by the presence of `ranges`/`supported`."},"MarketsTickSizeResolved":{"type":"object","required":["provider","contract_id","ranges","min_tick","source","synthetic"],"properties":{"provider":{"type":"string","enum":["kalshi","polymarket"]},"contract_id":{"type":"string"},"asset_id":{"type":["string","null"],"description":"Echoed back only for polymarket; null for kalshi."},"ranges":{"type":"array","items":{"$ref":"#/components/schemas/MarketsTickRange"},"description":"Polymarket always has exactly one range covering [0,1]; Kalshi may have several tapered ranges."},"min_tick":{"type":"string","description":"Decimal string — the finest step across all ranges.","example":"0.01"},"source":{"type":"string","enum":["metadata_cache","polymarket_clob","kalshi_price_ranges","kalshi_tick_size_deprecated"]},"synthetic":{"type":"boolean","description":"True only for the deprecated Kalshi flat-tick_size fallback (price_ranges absent)."},"price_level_structure":{"type":["string","null"],"description":"Kalshi's raw price_level_structure field, when present. Always null for polymarket."}}},"MarketsTickRange":{"type":"object","properties":{"start":{"type":"string","example":"0"},"end":{"type":"string","example":"1"},"step":{"type":"string","example":"0.001"}}},"MarketsTickSizeUnsupported":{"type":"object","required":["provider","contract_id","supported","reason"],"properties":{"provider":{"type":"string","enum":["predictfun"]},"contract_id":{"type":"string"},"supported":{"type":"boolean","enum":[false]},"reason":{"type":"string","example":"predict.fun exposes no per-market tick endpoint or tick change event; treat its tick as static/default. See docs/exchange-docs."}}},"MarketsMetadataResponse":{"type":"object","required":["ticker","provider","name","description","resolution_rules","contract","images","ctf_neg_risk","extra","active"],"properties":{"ticker":{"type":"string"},"provider":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"resolution_rules":{"$ref":"#/components/schemas/MarketsResolutionRules"},"contract":{"$ref":"#/components/schemas/MarketsContractInfo"},"images":{"$ref":"#/components/schemas/MarketsImages"},"external_url":{"type":["string","null"]},"condition_id":{"type":["string","null"],"description":"Only populated for some response paths; null when the response comes from a provider-API fallback."},"event_id":{"type":["string","null"],"description":"Only populated for some response paths."},"ctf_neg_risk":{"type":"boolean","default":false},"extra":{"type":"object","additionalProperties":true,"description":"Raw upstream/provider payload, plus router-side enrichment: for polymarket, `clobRewards` ([{rewardsDailyRate}] or []), `rewardsMaxSpread`, `rewardsMinSize`; for predictfun, `rewards.current` (active reward-window object or null).\n"},"active":{"type":"boolean","default":true,"description":"False if the market is delisted/resolved and not tradable."}}},"MarketsResolutionRules":{"type":"object","required":["primary"],"properties":{"primary":{"type":"string"},"secondary":{"type":["string","null"]},"source":{"type":["string","null"]}}},"MarketsContractInfo":{"type":"object","properties":{"tick_size":{"type":["number","null"]},"min_price":{"type":["number","null"]},"max_price":{"type":["number","null"]},"lot_size":{"type":["number","null"]},"quote_currency":{"type":["string","null"]},"settlement_ts":{"type":["string","null"],"description":"Only populated for some response paths; null otherwise."},"expires_at":{"type":["string","null"]}}},"MarketsImages":{"type":"object","properties":{"icon":{"type":["string","null"]},"banner":{"type":["string","null"]}}},"MarketsBatchMetadataRequest":{"type":"object","required":["contracts"],"properties":{"contracts":{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/MarketsBatchMetadataRequestItem"}},"titles_only":{"type":"boolean","default":false,"description":"When true, skips the API fallback, category inference, and tag-icon enrichment passes — returns whatever was already cached directly."}}},"MarketsBatchMetadataRequestItem":{"type":"object","required":["ticker","provider"],"properties":{"ticker":{"type":"string"},"provider":{"type":"string"}}},"MarketsBatchMetadataResponse":{"type":"object","description":"Metadata items keyed by the requested ticker.","additionalProperties":{"$ref":"#/components/schemas/MarketsBatchMetadataItem"}},"MarketsBatchMetadataItem":{"type":"object","required":["ticker","found"],"properties":{"ticker":{"type":"string"},"found":{"type":"boolean"},"title":{"type":["string","null"]},"event_title":{"type":["string","null"]},"provider":{"type":["string","null"]},"status":{"type":["string","null"]},"image":{"type":["string","null"]},"category":{"type":["string","null"],"description":"May be backfilled by ticker-pattern/title-keyword inference or by a tag slug when the underlying record has none."},"end_date":{"type":["string","null"]},"open_time":{"type":["string","null"]},"tag_icon":{"type":["string","null"],"description":"Highest-precedence active PlatformTag icon for this market."},"yes_sub_title":{"type":["string","null"],"description":"Custom binary side label (e.g. Hyperliquid team names). Null falls back to \"Yes\" on the frontend."},"no_sub_title":{"type":["string","null"]},"outcome_label":{"type":["string","null"],"description":"Short sibling-differentiating label for multi-outcome events (Polymarket groupItemTitle / Kalshi yes_sub_title)."},"resolved_outcome":{"type":["string","null"],"enum":[true,"no","void",null],"description":"Winning side for already-resolved binary markets; null if unresolved or not surfaced by the provider."},"outcome_pair":{"type":["array","null"],"items":{"type":"string"},"description":"The two real outcome labels for a non-Yes/No binary market (moneyline/spread/O-U), ordered to match outcome index 0/1."}}},"MarketsOutcomesResponse":{"type":"object","required":["event_title","outcomes","is_grouped","event_groups","event_titles"],"properties":{"event_title":{"type":["string","null"]},"image":{"type":["string","null"]},"icon":{"type":["string","null"]},"provider":{"type":["string","null"]},"category":{"type":["string","null"]},"outcomes":{"type":"array","items":{"$ref":"#/components/schemas/MarketsOutcomeItem"}},"is_grouped":{"type":"boolean"},"matched_market_id":{"type":["string","null"],"description":"The outcome entry that corresponds to the queried market_id, if found among the returned outcomes."},"event_groups":{"type":"object","additionalProperties":{"type":"string"},"description":"Maps every requested id (market_id plus each all_ids entry, plus every sibling id discovered while resolving) to its event_id."},"event_titles":{"type":"object","additionalProperties":{"type":"string"},"description":"Maps every event_id referenced in event_groups to its title."}}},"MarketsOutcomeItem":{"type":"object","properties":{"market_id":{"type":"string"},"title":{"type":"string"},"outcome_label":{"type":"string"},"price":{"type":"number","description":"0-1 decimal probability (stored cents / 100.0) — NOT the platform's usual 0-100 cents scale.","example":0.62},"volume_total":{"type":"number"},"volume_24h":{"type":"number"},"volume_1h":{"type":"number"},"token_id":{"type":["string","null"]},"condition_id":{"type":["string","null"]},"image":{"type":["string","null"],"description":"Only present when served from the discover cache, not from the direct upstream-provider fallback path."},"icon":{"type":["string","null"]},"expiration":{"type":["string","null"]}}},"MarketsCryptoResponse":{"type":"object","required":["markets","window_offset"],"properties":{"markets":{"type":"array","items":{"$ref":"#/components/schemas/MarketsCryptoMarket"}},"window_offset":{"type":"integer"}}},"MarketsCryptoMarket":{"type":"object","required":["symbol","name","market_id","provider_id","is_settled"],"properties":{"symbol":{"type":"string","enum":["BTC","ETH","SOL","XRP","DOGE","HYPE","BNB"]},"name":{"type":"string","description":"Human-readable market title (Kalshi titles get an appended open/close time range)."},"market_id":{"type":"string","description":"Kalshi ticker or Polymarket/predict.fun market id."},"token_id":{"type":["string","null"],"description":"Polymarket/predict.fun CLOB token id (the \"Yes\" token where resolvable). Absent for Kalshi entries."},"provider_id":{"type":"integer"},"price":{"type":["number","null"],"description":"0-1 decimal probability (mid of best bid/ask, last trade, or settlement snap to 0/1 for past windows) — NOT the platform's usual 0-100 cents scale. Null while a market has no orderbook yet.","example":0.47},"is_settled":{"type":"boolean","description":"True for past windows and any market whose settlement/resolution was found; the price is then snapped to exactly 0.0 or 1.0."},"is_upcoming":{"type":"boolean","description":"Only present on some current-window Kalshi entries — true if the market is in Kalshi's \"initialized\"/\"unopened\" pre-launch state."},"open_time":{"type":["string","null"],"description":"Only present on some current-window Kalshi entries."}}},"MarketsOracleHistoryResponse":{"type":"object","description":"Price-history points keyed by oracle symbol (btc-usd/eth-usd/sol-usd/xrp-usd), each array sorted ascending by timestamp.","additionalProperties":{"type":"array","items":{"$ref":"#/components/schemas/MarketsOraclePricePoint"}}},"MarketsOraclePricePoint":{"type":"object","properties":{"price":{"type":"number","example":97123.45},"timestamp":{"type":"integer","description":"Epoch milliseconds."}}},"MarketsPtbResponse":{"type":"object","description":"Keyed by window (1m/5m/15m/1h/4h/1d), each value keyed by symbol.","additionalProperties":{"$ref":"#/components/schemas/MarketsPtbWindowValues"}},"MarketsPtbWindowValues":{"type":"object","description":"Keyed by oracle symbol (btc-usd/eth-usd/sol-usd/xrp-usd). A symbol is omitted entirely if no price could be resolved for this window.","additionalProperties":{"$ref":"#/components/schemas/MarketsPtbValue"}},"MarketsPtbValue":{"type":"object","required":["price","timestamp_ms","game_start_ms"],"properties":{"price":{"type":"number","example":97000.5},"timestamp_ms":{"type":"integer","description":"Actual epoch-ms timestamp of the oracle sample used (may differ slightly from game_start_ms)."},"game_start_ms":{"type":"integer","description":"Epoch-ms start of the requested window (the \"price to beat\" anchor)."}}},"MarketsEquitySnapshotResponse":{"type":"object","required":["prices","cached"],"properties":{"prices":{"type":"object","description":"Keyed by the requested (upper-cased) symbol. Either the full cache hit set or just the freshly-fetched subset — see `cached`.","additionalProperties":{"$ref":"#/components/schemas/MarketsEquityPrice"}},"cached":{"type":"boolean","description":"True only if every requested symbol was already cached and `prices` is that full cached set. False means `prices` contains only symbols that had to be freshly fetched for this request."}}},"MarketsEquityPrice":{"type":"object","properties":{"symbol":{"type":"string","description":"The internal (Kairos-side) symbol, which may differ from the upstream ticker.","example":"AAPL"},"price":{"type":"number","example":231.45},"previousClose":{"type":["number","null"]},"currency":{"type":"string","default":"USD"},"marketState":{"type":"string","example":"REGULAR"},"timestamp":{"type":"integer","description":"Epoch milliseconds of the quote (regularMarketTime * 1000)."}}},"CandlesCandle":{"type":"object","description":"One OHLCV bar. `token_id` is only present (never emitted as null) when the underlying series is keyed by an outcome token rather than a plain contract id.","required":["contract_id","timeframe_seconds","bucket_start","open","high","low","close","volume"],"properties":{"contract_id":{"type":"string","description":"Contract/market identifier this bar belongs to.","example":"KXPRESPOLAND-24-DT"},"timeframe_seconds":{"type":"integer","description":"Bucket width in seconds.","example":60},"bucket_start":{"type":"string","format":"date-time","description":"Bucket start time, ISO 8601 UTC.","example":"2026-07-21T14:32:00+00:00"},"open":{"type":"number","description":"Opening price, 0-100 cents scale.","example":63.5},"high":{"type":"number","description":"High price in the bucket, 0-100 cents scale.","example":64},"low":{"type":"number","description":"Low price in the bucket, 0-100 cents scale.","example":63},"close":{"type":"number","description":"Closing price, 0-100 cents scale.","example":63.8},"volume":{"type":"integer","description":"Traded size (contracts/shares) within the bucket.","example":1250},"token_id":{"type":"string","description":"Outcome token identifier, present only when this series is token-scoped (e.g. multi-outcome Polymarket markets).","example":"71321045679252212594626385532706912750332728571942532289631379312455583992563"}}},"CandlesSeriesResponse":{"type":"object","required":["candles"],"properties":{"candles":{"type":"array","items":{"$ref":"#/components/schemas/CandlesCandle"}}}},"CandlesBatchRequestItem":{"type":"object","required":["provider","contract_id","timeframe_seconds","start","end"],"properties":{"provider":{"type":"string","example":"kalshi"},"contract_id":{"type":"string","maxLength":128,"example":"KXPRESPOLAND-24-DT"},"timeframe_seconds":{"type":"integer","enum":[1,60,300,900,3600,14400,86400],"example":60},"start":{"type":"string","format":"date-time","example":"2026-07-21T00:00:00Z"},"end":{"type":"string","format":"date-time","example":"2026-07-22T00:00:00Z"},"outcome":{"type":"integer","minimum":0,"default":0,"description":"Zero-based outcome index. Omitted/null defaults to 0; negative or non-integer values return 400."},"rebuild":{"type":"boolean","default":false,"description":"Bypass the cache for this item and force a fresh fetch."}}},"CandlesBatchRequest":{"type":"object","required":["requests"],"description":"`requests` is the primary field name; the router also accepts a legacy `items` key with the identical array shape as a fallback if `requests` is absent.\n","properties":{"requests":{"type":"array","minItems":1,"maxItems":200,"items":{"$ref":"#/components/schemas/CandlesBatchRequestItem"}}}},"CandlesBatchResultItem":{"type":"object","required":["index","candles"],"properties":{"index":{"type":"integer","description":"Position of this result, matching the index of the corresponding item in the request's `requests` array.","example":0},"candles":{"type":"array","items":{"$ref":"#/components/schemas/CandlesCandle"}},"error":{"type":"string","description":"Present only when this specific item failed while other items in the batch succeeded from cache. `candles` is `[]` in that case.","example":"Candle batch fetch failed"}}},"CandlesBatchResponse":{"type":"object","required":["results"],"properties":{"results":{"type":"array","description":"Index-ordered, one entry per request item.","items":{"$ref":"#/components/schemas/CandlesBatchResultItem"}}}},"TradesMetrics":{"type":"object","description":"Aggregate volume/pressure metrics. Split by outcome (outcome_0 vs outcome_1), not by buy/sell direction — trade direction is not reliably derivable from every provider's exchange data.\n","required":["volume_usd","outcome_0_volume_usd","outcome_1_volume_usd","outcome_0_pressure_pct","trade_count","window_seconds","coverage_pct","source","indexing"],"properties":{"volume_usd":{"type":"number","description":"Total notional volume in the window, in USD.","example":154320.55},"outcome_0_volume_usd":{"type":"number","description":"Notional volume attributed to the first/primary outcome (e.g. \"yes\").","example":98210.1},"outcome_1_volume_usd":{"type":"number","description":"Notional volume attributed to the second outcome (e.g. \"no\").","example":56110.45},"outcome_0_pressure_pct":{"type":"number","description":"Share of total volume_usd attributed to outcome_0, as a percentage (0-100).","example":63.65},"trade_count":{"type":"integer","description":"Number of trades included in the aggregation.","example":842},"window_seconds":{"type":"integer","description":"Window width the metrics were computed over, in seconds.","example":86400},"coverage_pct":{"type":"number","description":"Estimated data coverage for the window as a percentage (0-100); 100 for live upstream proxies (Kalshi/Polymarket) and for fully-ingested windows.","example":100},"source":{"type":"string","description":"Opaque internal marker indicating how the response was produced. NOT a stable part of the contract — its value may change without notice; do not depend on or switch on it."},"indexing":{"type":"boolean","description":"True if an ingestion/backfill job was triggered or is in progress for this window (only meaningful when the caller passed `trigger_ingest=true`); coverage may be incomplete when true.","example":false}}},"TradesTrade":{"type":"object","description":"A single normalized trade, as returned by `/trades/history`. Includes legacy `yes_price`/`no_price`/`taker_side` fields derived from `price`/`outcome` for backward compatibility with older consumers.\n","required":["trade_id","order_id","contract_id","size","price","outcome","timestamp","yes_price","no_price","taker_side"],"properties":{"trade_id":{"type":"string","example":"kalshi:KXPRESPOLAND-24-DT:9f2a1c"},"order_id":{"type":"string","nullable":true,"description":"Venue order identifier or hash shared by fills from the same order. Null when the source venue does not provide an order identifier. This is not a Kairos order UUID.","example":"0x9f2a1c...7bd4"},"contract_id":{"type":"string","example":"KXPRESPOLAND-24-DT"},"size":{"type":"integer","description":"Traded size (contracts/shares).","example":50},"price":{"type":"number","description":"Trade price of the traded outcome, 0-100 cents scale.","example":63},"outcome":{"type":"string","description":"Outcome name/index the trade was executed against (e.g. \"yes\", \"no\", or a token-based outcome label).","example":"yes"},"timestamp":{"type":"number","description":"Unix timestamp, seconds.","example":1753142400},"token_id":{"type":"string","description":"Outcome token identifier, present only when applicable."},"metadata":{"type":"string","description":"Provider-specific JSON blob serialized as a string, present only when available."},"taker_address":{"type":"string","description":"Taker wallet address, present only for on-chain venues where it's known."},"maker_address":{"type":"string","description":"Counterparty wallet/contract address; may be the canonical exchange contract on aggressor-summary rows or a real wallet on maker-fill legs. Present only when known."},"side":{"type":"string","enum":["BUY","SELL"],"description":"Trade direction (distinct from `outcome`), present only when known."},"yes_price":{"type":"number","description":"Legacy field: price expressed on the 'yes' outcome, 0-100 scale, derived from `price`/`outcome`.","example":63},"no_price":{"type":"number","description":"Legacy field: price expressed on the 'no' outcome, 0-100 scale, derived from `price`/`outcome`.","example":37},"taker_side":{"type":"string","description":"Legacy alias, always equal to `outcome`.","example":"yes"}}},"TradesHistoryResponse":{"type":"object","required":["trades","has_more","source","coverage_hours","indexing"],"properties":{"trades":{"type":"array","items":{"$ref":"#/components/schemas/TradesTrade"}},"has_more":{"type":"boolean","description":"True if more trades exist before the oldest trade in this response (pagination via `before`)."},"oldest_available_ts":{"type":"number","nullable":true,"description":"Unix timestamp (seconds) of the oldest trade Kairos has ingested for this contract, or null if unknown.","example":1750000000},"source":{"type":"string","description":"Opaque internal marker indicating how the response was served. NOT a stable part of the contract — its value may change without notice; do not depend on or switch on it."},"coverage_hours":{"type":"number","description":"Hours of trade history coverage available/considered for this response.","example":24},"indexing":{"type":"boolean","description":"True if ingestion was triggered/in progress for this window (only meaningful with `trigger_ingest=true`).","example":false}}},"TradesMetricsResponse":{"type":"object","required":["metrics"],"properties":{"metrics":{"$ref":"#/components/schemas/TradesMetrics"}}},"TradesKalshiRawTrade":{"type":"object","description":"Raw trade object as returned by Kalshi's `GET /trade-api/v2/markets/trades`, passed through unmodified. Fields shown are the ones Kalshi documents/observed in practice; the API does not restrict or re-type them.\n","additionalProperties":true,"properties":{"trade_id":{"type":"string"},"ticker":{"type":"string"},"count":{"type":"integer","description":"Contracts traded."},"created_time":{"type":"string","format":"date-time"},"yes_price":{"type":"integer","description":"0-100 cents scale."},"no_price":{"type":"integer","description":"0-100 cents scale."},"taker_side":{"type":"string","enum":[true,false]}}},"TradesKalshiResponse":{"type":"object","description":"The raw Kalshi API response object, spread as-is, with a `metrics` field injected by Kairos. Any other fields Kalshi returns (e.g. a pagination `cursor`) pass through unchanged.\n","additionalProperties":true,"required":["trades","metrics"],"properties":{"trades":{"type":"array","items":{"$ref":"#/components/schemas/TradesKalshiRawTrade"}},"cursor":{"type":"string","description":"Upstream pagination cursor, passed through when present."},"metrics":{"$ref":"#/components/schemas/TradesMetrics"}}},"TradesPolymarketRawTrade":{"type":"object","description":"Raw trade object as returned by Polymarket's `GET https://data-api.polymarket.com/trades`, passed through unmodified.\n","additionalProperties":true,"properties":{"proxyWallet":{"type":"string"},"side":{"type":"string","enum":["BUY","SELL"]},"asset":{"type":"string","description":"Outcome token id."},"conditionId":{"type":"string"},"size":{"type":"number"},"price":{"type":"number","description":"Upstream price, 0-1 decimal scale (not the 0-100 scale Kairos normalizes to elsewhere)."},"timestamp":{"type":"integer","description":"Unix seconds."},"transactionHash":{"type":"string"},"outcome":{"type":"string"}}},"TradesPolymarketResponse":{"type":"object","required":["trades","metrics"],"properties":{"trades":{"type":"array","items":{"$ref":"#/components/schemas/TradesPolymarketRawTrade"}},"metrics":{"type":"object","additionalProperties":true,"description":"A `TradesMetrics` object in the normal case. When `market` could not be resolved to a condition ID, this is a literal empty object `{}` and `trades` is `[]` — no upstream call was made.\n"}}},"TraderPnlDataPoint":{"type":"object","description":"Single point on a trader's realized-PnL time series.","required":["timestamp","realized_pnl"],"properties":{"timestamp":{"type":"string","format":"date-time","description":"UTC timestamp of this data point.","example":"2026-07-15T00:00:00Z"},"realized_pnl":{"type":"string","format":"decimal","description":"Cumulative realized PnL (USD) at this point in time. Serialized as a decimal string.","example":"1250.4382"}}},"TraderPnlHistoryResponse":{"type":"object","description":"Time-series realized-PnL data for a wallet over a requested time range, plus a summary of the change over that range.\n","required":["wallet_address","time_range","data_points","start_pnl","end_pnl","pnl_change","current_realized_pnl"],"properties":{"wallet_address":{"type":"string","example":"0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"},"time_range":{"type":"string","enum":["1D","1W","1M","ALL"],"example":"1W"},"data_points":{"type":"array","items":{"$ref":"#/components/schemas/TraderPnlDataPoint"}},"start_pnl":{"type":"string","format":"decimal","description":"Realized PnL at the start of the window.","example":"800.00"},"end_pnl":{"type":"string","format":"decimal","description":"Realized PnL at the end of the window.","example":"1250.4382"},"pnl_change":{"type":"string","format":"decimal","description":"Absolute change in realized PnL over the window (end_pnl - start_pnl).","example":"450.4382"},"pnl_change_percent":{"type":"string","format":"decimal","nullable":true,"description":"Percentage change over the window; null if start_pnl is zero/undefined.","example":"56.30"},"current_realized_pnl":{"type":"string","format":"decimal","description":"Wallet's current total realized PnL (as of now, not bounded to the window).","example":"1250.4382"},"current_total_pnl":{"type":"string","format":"decimal","nullable":true,"description":"Current realized + unrealized PnL combined, when available.","example":"1875.10"}}},"TraderPosition":{"type":"object","description":"A single current or historical position held by a trader.","required":["market_id","token_id","outcome","size","entry_price","cost_basis","realized_pnl","status","provider"],"properties":{"market_id":{"type":"string","example":"0xabc123condition"},"market_name":{"type":"string","nullable":true,"example":"Will the Fed cut rates in September?"},"icon":{"type":"string","nullable":true,"description":"Market icon/image URL.","example":"https://cdn.kairos.trade/markets/abc123.png"},"token_id":{"type":"string","example":"10723948572...4"},"outcome":{"type":"string","description":"Outcome label — YES/NO or a token symbol.","example":"YES"},"size":{"type":"string","format":"decimal","description":"Position size (>= 0).","example":"150.0"},"entry_price":{"type":"string","format":"decimal","description":"Average entry price (>= 0), 0-1 scale.","example":"0.42"},"current_price":{"type":"string","format":"decimal","nullable":true,"description":"Current mark price (>= 0), 0-1 scale.","example":"0.55"},"cost_basis":{"type":"string","format":"decimal","description":"Total USD cost basis for this position (>= 0).","example":"63.00"},"unrealized_pnl":{"type":"string","format":"decimal","nullable":true,"example":"19.50"},"realized_pnl":{"type":"string","format":"decimal","description":"Realized PnL booked against this position so far.","example":"0.00"},"status":{"type":"string","enum":["OPEN","CLOSED"]},"opened_at":{"type":"string","format":"date-time","nullable":true},"closed_at":{"type":"string","format":"date-time","nullable":true},"provider":{"type":"string","description":"Venue the position was traded on.","default":"polymarket","example":"polymarket"}}},"TraderPerformance":{"type":"object","description":"Aggregated trader performance metrics, always computed over the full inventory (not a paginated page).","required":["wallet_address","total_realized_pnl","total_unrealized_pnl","total_pnl","open_positions","closed_positions","total_positions","winning_positions","losing_positions","win_rate","total_volume","markets_traded","last_updated"],"properties":{"wallet_address":{"type":"string","example":"0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"},"total_realized_pnl":{"type":"string","format":"decimal","example":"1250.4382"},"total_unrealized_pnl":{"type":"string","format":"decimal","example":"300.00"},"total_pnl":{"type":"string","format":"decimal","example":"1550.4382"},"roi_percent":{"type":"string","format":"decimal","default":"0","example":"12.75"},"open_positions":{"type":"integer","minimum":0,"example":4},"closed_positions":{"type":"integer","minimum":0,"example":27},"total_positions":{"type":"integer","minimum":0,"example":31},"positions_value":{"type":"string","format":"decimal","default":"0","description":"Cost basis (deployed capital) of OPEN positions, summed over the full inventory.","example":"420.00"},"winning_positions":{"type":"integer","minimum":0,"example":18},"losing_positions":{"type":"integer","minimum":0,"example":9},"win_rate":{"type":"number","format":"double","minimum":0,"maximum":1,"description":"Win rate over closed, resolved positions.","example":0.6667},"total_volume":{"type":"string","format":"decimal","minimum":"0","example":"18420.55"},"markets_traded":{"type":"integer","minimum":0,"example":12},"join_date":{"type":"string","format":"date-time","nullable":true,"description":"Polymarket account join date, when known."},"profile_views":{"type":"integer","default":0,"example":340},"largest_win":{"type":"string","format":"decimal","nullable":true,"example":"512.30"},"last_updated":{"type":"string","format":"date-time","example":"2026-07-22T14:03:11Z"}}},"TraderSummaryResponse":{"type":"object","description":"A wallet's performance stats alone, the same figures as `performance` on the positions endpoint. `performance` is null for a venue whose stats come only from the full positions build.\n","required":["wallet_address","performance"],"properties":{"wallet_address":{"type":"string","example":"0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"},"performance":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/TraderPerformance"}]}}},"TraderAnalysisWindow":{"type":"object","description":"Realized PnL and buys over whole UTC days ending today.","required":["window","realized_pnl","buy_count"],"properties":{"window":{"type":"string","enum":["1D","7D","30D","ALL"]},"realized_pnl":{"type":"number","description":"Realized PnL in USD.","example":15.3},"buy_count":{"type":"integer","minimum":0,"example":16}}},"TraderAnalysisDay":{"type":"object","required":["day","realized_pnl"],"properties":{"day":{"type":"string","format":"date","example":"2026-09-28"},"realized_pnl":{"type":"number","description":"Realized PnL in USD booked that UTC day.","example":-25.82}}},"TraderAnalysisWinLoss":{"type":"object","description":"Lifetime closed-position counts. `win_rate` is winning over winning plus losing, 0 when none has closed.","required":["winning_positions","losing_positions","win_rate"],"properties":{"winning_positions":{"type":"integer","minimum":0,"example":13},"losing_positions":{"type":"integer","minimum":0,"example":11},"win_rate":{"type":"number","minimum":0,"maximum":1,"example":0.5417}}},"TraderRoiDistribution":{"type":"object","description":"Closed positions counted by ROI, the position's realized PnL over the total cost bought for it. Positions with no bought cost are left out.","required":["gt_500","between_200_500","between_0_200","between_neg_50_0","lt_neg_50"],"properties":{"gt_500":{"type":"integer","minimum":0,"description":"ROI above 500%."},"between_200_500":{"type":"integer","minimum":0,"description":"ROI above 200% up to 500%."},"between_0_200":{"type":"integer","minimum":0,"description":"ROI from 0% to 200%."},"between_neg_50_0":{"type":"integer","minimum":0,"description":"ROI from -50% up to, but not including, 0%."},"lt_neg_50":{"type":"integer","minimum":0,"description":"ROI below -50%."}}},"TraderAnalysisResponse":{"type":"object","description":"A trader profile's analysis panel. `supported` is false, with empty `windows` and `daily` and null `win_loss` and `roi_distribution`, for a venue whose trades do not flow through the lot allocator.\n","required":["wallet_address","supported","windows","daily","win_loss","roi_distribution"],"properties":{"wallet_address":{"type":"string","example":"0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"},"provider":{"type":"string","nullable":true,"example":"predictfun"},"supported":{"type":"boolean"},"windows":{"type":"array","items":{"$ref":"#/components/schemas/TraderAnalysisWindow"}},"daily":{"type":"array","description":"The last 90 UTC days that have activity, oldest first.","items":{"$ref":"#/components/schemas/TraderAnalysisDay"}},"win_loss":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/TraderAnalysisWinLoss"}]},"roi_distribution":{"nullable":true,"description":"Null when unsupported, or when the ROI read failed and the rest of the panel was served without it.","allOf":[{"$ref":"#/components/schemas/TraderRoiDistribution"}]}}},"TraderPositionsResponse":{"type":"object","description":"A page of a wallet's positions plus performance stats derived from the full inventory in the same scan.\n","required":["wallet_address","performance","open_positions","closed_positions"],"properties":{"wallet_address":{"type":"string","example":"0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"},"performance":{"$ref":"#/components/schemas/TraderPerformance"},"open_positions":{"type":"array","items":{"$ref":"#/components/schemas/TraderPosition"}},"closed_positions":{"type":"array","items":{"$ref":"#/components/schemas/TraderPosition"}},"has_more":{"type":"boolean","default":false,"description":"True when more positions exist beyond this page (open+closed combined, sorted by value)."}}},"TraderTradeRecord":{"type":"object","description":"A single trade/fill record.","required":["trade_id","order_id","market_id","token_id","side","size","price","timestamp","provider"],"properties":{"trade_id":{"type":"string","example":"trd_9f2c3a1b"},"order_id":{"type":"string","nullable":true,"description":"Parent venue order identifier/hash shared by fills from the same order. For Predict.fun this is the venue EIP-712 order hash. Null when the provider or lifecycle row has no authoritative venue order identifier. This is not a Kairos order UUID.","example":"0x9f2a1c...7bd4"},"market_id":{"type":"string","example":"0xabc123condition"},"market_name":{"type":"string","nullable":true,"example":"Will the Fed cut rates in September?"},"icon":{"type":"string","nullable":true,"example":"https://cdn.kairos.trade/markets/abc123.png"},"token_id":{"type":"string","example":"10723948572...4"},"outcome":{"type":"string","nullable":true,"example":"YES"},"side":{"type":"string","enum":["BUY","SELL"]},"size":{"type":"string","format":"decimal","description":"Trade size (>= 0).","example":"25.0"},"price":{"type":"string","format":"decimal","description":"Fill price (>= 0), 0-1 scale.","example":"0.47"},"timestamp":{"type":"string","format":"date-time","example":"2026-07-20T09:14:02Z"},"tx_hash":{"type":"string","nullable":true,"example":"0xfeedface...beef"},"realized_pnl":{"type":"string","format":"decimal","nullable":true,"description":"Realized PnL booked by this fill (populated for SELLs from the FIFO ledger; null/0 for BUYs).","example":"3.75"},"provider":{"type":"string","default":"polymarket","example":"polymarket"}}},"TraderRecentTradesResponse":{"type":"object","description":"A standalone page of trade history, served independently of the full profile build.\n","required":["wallet_address","trades"],"properties":{"wallet_address":{"type":"string","example":"0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"},"trades":{"type":"array","items":{"$ref":"#/components/schemas/TraderTradeRecord"}},"has_more":{"type":"boolean","default":false}}},"TraderTopHolder":{"type":"object","description":"A single holder of a market's outcome token.","required":["proxyWallet","amount","outcomeIndex","displayUsernamePublic","verified"],"properties":{"proxyWallet":{"type":"string","description":"Holder's wallet address.","example":"0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"},"amount":{"type":"number","format":"double","description":"Token amount held.","example":15230.5},"outcomeIndex":{"type":"integer","description":"Index of the outcome token held (0 = first outcome, 1 = second, ...).","example":0},"displayUsernamePublic":{"type":"boolean","description":"Whether the holder has opted to publicly display their username."},"verified":{"type":"boolean","description":"Whether the holder has a verified badge."},"name":{"type":"string","description":"Present only when the provider returned a display name.","example":"whale_trader_99"},"pseudonym":{"type":"string","example":"SilentOwl-4821"},"bio":{"type":"string","example":"Prediction market degen."},"asset":{"type":"string","description":"Underlying asset identifier for the held token, when the provider supplies one."},"profileImage":{"type":"string","example":"https://cdn.polymarket.com/avatars/abc.png"},"profileImageOptimized":{"type":"string","example":"https://cdn.polymarket.com/avatars/abc-opt.png"}}},"TraderTopHoldersToken":{"type":"object","description":"Top holders for a single outcome token.","required":["token","holders"],"properties":{"token":{"type":"string","description":"Outcome token id.","example":"10723948572...4"},"holders":{"type":"array","items":{"$ref":"#/components/schemas/TraderTopHolder"}}}},"TraderTopHoldersResponse":{"type":"array","description":"Top holders grouped by outcome token, one entry per requested market/token combination returned by the provider. The endpoint returns this array directly as the response body (not wrapped in an object).\n","items":{"$ref":"#/components/schemas/TraderTopHoldersToken"}},"TraderSearchUserInfo":{"type":"object","description":"An associated Polymarket user record linked to a profile (e.g. multi-role accounts).","required":["id","creator","mod"],"properties":{"id":{"type":"string","example":"usr_polymarket_884211"},"creator":{"type":"boolean","description":"Whether this user record has market-creator privileges."},"mod":{"type":"boolean","description":"Whether this user record has moderator privileges."}}},"TraderSearchProfile":{"type":"object","description":"Public trader profile fields returned by the upstream provider.","required":["displayUsernamePublic","verifiedBadge","users"],"properties":{"proxyWallet":{"type":"string","nullable":true,"example":"0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"},"name":{"type":"string","nullable":true,"example":"Jane Trader"},"pseudonym":{"type":"string","nullable":true,"example":"QuietFalcon-2201"},"bio":{"type":"string","nullable":true,"example":"Macro and elections."},"profileImage":{"type":"string","nullable":true,"example":"https://cdn.polymarket.com/avatars/jane.png"},"xUsername":{"type":"string","nullable":true,"example":"janetrades"},"verifiedBadge":{"type":"boolean"},"displayUsernamePublic":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time","nullable":true,"example":"2023-11-02T18:20:44Z"},"users":{"type":"array","items":{"$ref":"#/components/schemas/TraderSearchUserInfo"}}}},"TraderSearchResponse":{"type":"object","description":"Result of a trader-profile search. `profile` is null (with `error` populated) when the provider found no profile for the address.\n","required":["provider","address","profile","error"],"properties":{"provider":{"type":"string","example":"polymarket"},"address":{"type":"string","example":"0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"},"profile":{"allOf":[{"$ref":"#/components/schemas/TraderSearchProfile"}],"nullable":true},"error":{"type":"string","nullable":true,"description":"Provider-supplied error message when no profile was found.","example":null}}},"PnlProviderInfo":{"type":"object","description":"A PnL provider available through the merged-PnL pipeline.","required":["name","description"],"properties":{"name":{"type":"string","example":"polymarket"},"description":{"type":"string","example":"Polymarket prediction market PnL"}}},"PnlExchangeSummary":{"type":"object","description":"Per-exchange PnL summary within a merged PnL response.","required":["provider","wallet_address","total_realized_pnl","total_fees","total_cost_basis","winning_positions","losing_positions"],"properties":{"provider":{"type":"string","example":"polymarket"},"wallet_address":{"type":"string","example":"0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"},"total_realized_pnl":{"type":"number","format":"double","example":1250.4382},"total_fees":{"type":"number","format":"double","example":12.5},"total_cost_basis":{"type":"number","format":"double","example":8400},"winning_positions":{"type":"integer","example":18},"losing_positions":{"type":"integer","example":9}}},"PnlSummary":{"type":"object","description":"Merged PnL summary across all providers/wallets supplied for a user.","required":["user_id","total_realized_pnl","total_fees","total_cost_basis","total_winning_positions","total_losing_positions","by_exchange"],"properties":{"user_id":{"type":"string","example":"usr_9f3c2a1b"},"total_realized_pnl":{"type":"number","format":"double","example":1875.1},"total_fees":{"type":"number","format":"double","example":18.2},"total_cost_basis":{"type":"number","format":"double","example":12400},"total_winning_positions":{"type":"integer","example":25},"total_losing_positions":{"type":"integer","example":11},"by_exchange":{"type":"object","description":"Keyed by provider name.","additionalProperties":{"$ref":"#/components/schemas/PnlExchangeSummary"}}}},"PnlRecord":{"type":"object","description":"A single merged PnL record.","required":["provider","market_id","timestamp","realized_pnl","fees"],"properties":{"provider":{"type":"string","example":"polymarket"},"market_id":{"type":"string","example":"0xabc123condition"},"timestamp":{"type":"string","description":"ISO-8601 timestamp string (empty string if the underlying record has no timestamp).","example":"2026-07-20T09:14:02+00:00"},"realized_pnl":{"type":"number","format":"double","example":24.15},"fees":{"type":"number","format":"double","example":0.35}}},"PnlUserPnlResponse":{"type":"object","description":"Full merged PnL response for a user, across the requested wallets/providers.\n","required":["user_id","summary","records","filters_applied"],"properties":{"user_id":{"type":"string","example":"usr_9f3c2a1b"},"summary":{"$ref":"#/components/schemas/PnlSummary"},"records":{"type":"array","items":{"$ref":"#/components/schemas/PnlRecord"}},"filters_applied":{"type":"object","description":"Echoes the effective filters used to build this response (wallets, providers, etc.).","additionalProperties":true},"total":{"type":"integer","default":0,"description":"Count of merged records the aggregator returned before pagination slicing, bounded by the per-provider fetch ceiling (min(limit+offset, 1000)) — not an authoritative full-history count.\n","example":143},"has_more":{"type":"boolean","default":false,"description":"True only when the aggregator definitively saw more records than this page returned."}}},"PnlWalletMarketTokenPnL":{"type":"object","description":"Per-token PnL row within a wallet/market hover payload.","required":["token_id","outcome","balance","avg_entry_price","cost_basis_usd","realized_pnl_usd","mark_price","position_value_usd","unrealized_pnl_usd"],"properties":{"token_id":{"type":"string","example":"10723948572...4"},"outcome":{"type":"string","example":"YES"},"balance":{"type":"number","format":"double","example":150},"avg_entry_price":{"type":"number","format":"double","example":0.42},"cost_basis_usd":{"type":"number","format":"double","example":63},"realized_pnl_usd":{"type":"number","format":"double","example":0},"mark_price":{"type":"number","format":"double","example":0.55},"position_value_usd":{"type":"number","format":"double","example":82.5},"unrealized_pnl_usd":{"type":"number","format":"double","example":19.5}}},"PnlWalletMarketPnlResponse":{"type":"object","description":"Live hover-on-wallet PnL for a single (wallet, market) pair. Returns a zeroed/empty payload (`tokens: []`) instead of erroring when the underlying PnL pipeline is disabled.\n","required":["provider_id","wallet_address","contract_id","tokens","total_unrealized_pnl_usd","total_realized_pnl_usd","total_position_value_usd"],"properties":{"provider_id":{"type":"string","enum":["polymarket","opinion","predictfun"],"example":"polymarket"},"wallet_address":{"type":"string","example":"0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"},"contract_id":{"type":"string","example":"0xabc123condition"},"tokens":{"type":"array","items":{"$ref":"#/components/schemas/PnlWalletMarketTokenPnL"}},"total_unrealized_pnl_usd":{"type":"number","format":"double","example":19.5},"total_realized_pnl_usd":{"type":"number","format":"double","example":0},"total_position_value_usd":{"type":"number","format":"double","example":82.5}}},"PnlWalletTotalsResponse":{"type":"object","description":"Wallet-wide PnL totals across all markets, refreshed hourly. Returns a zeroed payload (`market_count: 0`, `token_count: 0`) instead of erroring when the underlying PnL pipeline is disabled.\n","required":["provider_id","wallet_address","total_unrealized_pnl_usd","total_realized_pnl_usd","total_position_value_usd","total_cost_basis_usd","market_count","token_count","snapshot_ts"],"properties":{"provider_id":{"type":"string","enum":["polymarket","opinion","predictfun"],"example":"polymarket"},"wallet_address":{"type":"string","example":"0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"},"total_unrealized_pnl_usd":{"type":"number","format":"double","example":340.2},"total_realized_pnl_usd":{"type":"number","format":"double","example":1250.4382},"total_position_value_usd":{"type":"number","format":"double","example":980},"total_cost_basis_usd":{"type":"number","format":"double","example":640},"market_count":{"type":"integer","example":12},"token_count":{"type":"integer","example":19},"snapshot_ts":{"type":"string","format":"date-time","description":"ISO-8601 timestamp of the hourly snapshot this data was read from (or the current time when native_pnl_pipeline is disabled).","example":"2026-07-22T13:00:00Z"}}},"SearchBadRequestError":{"type":"object","description":"FastAPI `HTTPException(400)` body shape used across the search router.","required":["detail"],"properties":{"detail":{"type":"string","example":"Invalid provider: acme"}}},"DiscoverBadRequestError":{"type":"object","description":"FastAPI `HTTPException(400)` body shape used across the discover router.","required":["detail"],"properties":{"detail":{"type":"string","example":"Invalid sort_by: bogus. Must be one of: ['liquidity', 'newest', 'price', 'rewards', 'volume', 'volume_1h', 'volume_24h']"}}},"ProvidersNotFoundError":{"type":"object","required":["detail"],"properties":{"detail":{"type":"string","example":"Provider 'acme' not found"}}},"SearchResult":{"type":"object","description":"One market or event row from search results, post provider-visibility filtering.","required":["market_id","provider_id","provider","relevance_score"],"properties":{"market_id":{"type":"string","example":"KXPRES-28-DJT"},"provider_id":{"type":"integer","description":"Numeric provider id (1=kalshi, 2=polymarket, 3=opinion, 8=predictfun).","example":1},"provider":{"type":"string","example":"kalshi"},"event_id":{"type":"string","nullable":true},"event_name":{"type":"string","nullable":true},"name":{"type":"string","nullable":true,"description":"Market title."},"symbol":{"type":"string","nullable":true},"category":{"type":"string","nullable":true},"status":{"type":"string","nullable":true,"example":"open"},"expires_at":{"type":"string","format":"date-time","nullable":true},"relevance_score":{"type":"number","description":"Composite final score = text_score + business_score - penalty."},"volume_24h":{"type":"number","default":0},"outcome_label":{"type":"string","nullable":true},"series_key":{"type":"string","nullable":true,"description":"Polymarket seriesSlug or Kalshi series_ticker."},"series_title":{"type":"string","nullable":true},"text_score":{"type":"number","default":0},"business_score":{"type":"number","default":0},"penalty":{"type":"number","default":0,"minimum":0},"price":{"type":"number","nullable":true},"volume_1h":{"type":"number","default":0},"liquidity":{"type":"number","default":0},"image":{"type":"string","nullable":true},"icon":{"type":"string","nullable":true},"token_id":{"type":"string","nullable":true,"description":"First outcome's on-chain token id (legacy singular field). For a market with more than one outcome use `token_ids`/`outcomes` instead."},"condition_id":{"type":"string","nullable":true,"description":"Venue condition id (Polymarket 0x… condition hash), when the venue has one."},"token_ids":{"type":"array","items":{"type":"string"},"description":"Full on-chain outcome token roster, aligned with `outcomes`. Feed these to `/v1/synthetics` legs and `/v1/candles` without a second resolve call. Empty when the market has no on-chain tokens."},"outcomes":{"type":"array","items":{"type":"string"},"description":"Outcome labels aligned positionally with `token_ids`."},"type":{"type":"string","enum":["event"],"nullable":true,"description":"Only present (and only ever \"event\") on rows returned by /search/markets-and-events when type=event or both."}}},"SearchMeta":{"type":"object","properties":{"query":{"type":"string"},"returned":{"type":"integer","description":"Number of rows in `results` after provider-visibility filtering."},"requested":{"type":"integer","description":"The `limit` that was requested."},"query_time_ms":{"type":"number"},"include_expired":{"type":"boolean"},"provider_id":{"type":"string","nullable":true,"description":"Resolved/validated provider filter, or null if none."}}},"SearchMarketsResponse":{"type":"object","required":["results","meta"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/SearchResult"}},"meta":{"$ref":"#/components/schemas/SearchMeta"}}},"SearchMarketsAndEventsMeta":{"type":"object","properties":{"query":{"type":"string"},"returned":{"type":"integer"},"requested":{"type":"integer","description":"The `limit` that was requested."},"search_type":{"type":"string","enum":["market","event","both"]},"include_expired":{"type":"boolean"},"provider_id":{"type":"string","nullable":true}}},"SearchMarketsAndEventsResponse":{"type":"object","required":["results","meta"],"properties":{"results":{"type":"array","description":"Merged market + event rows, sorted by relevance_score descending, truncated to limit.","items":{"$ref":"#/components/schemas/SearchResult"}},"meta":{"$ref":"#/components/schemas/SearchMarketsAndEventsMeta"}}},"SearchEventGroup":{"type":"object","description":"One event's outcome markets, grouped server-side.","required":["event_id","market_count","markets"],"properties":{"event_id":{"type":"string"},"event_name":{"type":"string","nullable":true},"market_count":{"type":"integer","description":"Count of markets kept after provider-visibility filtering."},"representative":{"allOf":[{"$ref":"#/components/schemas/SearchResult"}],"nullable":true,"description":"Falls back to the first remaining market if the original representative's provider was filtered out."},"markets":{"type":"array","items":{"$ref":"#/components/schemas/SearchResult"}}}},"SearchOutcomeRef":{"type":"object","description":"One outcome within a classified MarketGroup (interactive ladder).","properties":{"id":{"type":"string"},"provider":{"type":"string"},"title":{"type":"string","nullable":true},"outcomeLabel":{"type":"string","nullable":true},"tokenId":{"type":"string","nullable":true},"price":{"type":"number","nullable":true},"op":{"type":"string","nullable":true,"description":"Comparison operator this outcome represents on the group's axis, e.g. '>=', '<'."},"value":{"type":"number","nullable":true,"description":"Threshold value on the group's axis."},"hi":{"type":"number","nullable":true,"description":"Upper bound, for range-shaped outcomes."}}},"SearchMarketGroup":{"type":"object","description":"An \"interactive ladder\" grouping of related markets (e.g. a set of over/under threshold markets on one axis), pre-computed by a background job and served from cache at search time.","required":["type","groupKey","provider","outcomes"],"properties":{"type":{"type":"string","enum":["group"]},"groupKey":{"type":"string"},"provider":{"type":"string"},"title":{"type":"string","nullable":true},"kind":{"type":"string","nullable":true,"description":"Classification kind, e.g. threshold ladder."},"axisLabel":{"type":"string","nullable":true},"unit":{"type":"string","nullable":true},"direction":{"type":"string","nullable":true},"confidence":{"type":"number","description":"Rounded to 3 decimal places."},"marketCount":{"type":"integer"},"category":{"type":"string","nullable":true},"image":{"type":"string","nullable":true},"icon":{"type":"string","nullable":true},"eventId":{"type":"string","nullable":true},"eventTitle":{"type":"string","nullable":true},"outcomes":{"type":"array","items":{"$ref":"#/components/schemas/SearchOutcomeRef"}}}},"SearchCorrelationCounterpart":{"type":"object","description":"A cross-venue market judged similar (similarity at or above a threshold) to the keyed result market_id. Enrichment fields (title/ticker/image/icon/expires_at) are only present when the counterpart market's metadata is available; otherwise they're absent.","required":["provider_id","market_id","similarity","method"],"properties":{"provider_id":{"type":"integer"},"market_id":{"type":"string"},"similarity":{"type":"number","minimum":0,"maximum":1},"method":{"type":"string","description":"How the correlation was derived, e.g. embedding/manual."},"title":{"type":"string"},"ticker":{"type":"string"},"image":{"type":"string","nullable":true},"icon":{"type":"string","nullable":true},"expires_at":{"type":"string","format":"date-time","nullable":true}}},"SearchSimpleMeta":{"type":"object","properties":{"query":{"type":"string"},"returned":{"type":"integer","description":"Count of results after provider-visibility filtering."},"query_time_ms":{"type":"number"},"total":{"type":"integer","nullable":true,"description":"Present only when include_total=true. Best-effort: the upstream total if no rows were filtered by visibility, else the filtered count."}}},"SearchSimpleResponse":{"type":"object","description":"Shared envelope for /search/simple and /search/resolve-url. Both always include results/meta; groups/singles appear only when the underlying search was grouped (always true for /search/simple with group_results=true, and for /search/resolve-url on an event URL). classifiedGroups/absorbedMarketIds are only ever attached by /search/simple (group_results=true); /search/resolve-url never sets them. correlations is attached by both, best-effort, and absent if no counterpart markets were found.","required":["results","meta"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/SearchResult"}},"groups":{"type":"array","items":{"$ref":"#/components/schemas/SearchEventGroup"}},"singles":{"type":"array","items":{"$ref":"#/components/schemas/SearchResult"}},"meta":{"$ref":"#/components/schemas/SearchSimpleMeta"},"classifiedGroups":{"type":"array","items":{"$ref":"#/components/schemas/SearchMarketGroup"}},"absorbedMarketIds":{"type":"array","items":{"type":"string"},"description":"Market/outcome ids already rendered inside classifiedGroups — the overlay should not also render them as standalone rows."},"correlations":{"type":"object","description":"Map of result market_id -> up to 4 similar cross-venue counterpart markets, sorted by similarity descending.","additionalProperties":{"type":"array","items":{"$ref":"#/components/schemas/SearchCorrelationCounterpart"}}}}},"SearchSuggestion":{"type":"object","required":["text","type","frequency"],"properties":{"text":{"type":"string","description":"Suggested query text (market/series/category name)."},"type":{"type":"string","description":"Suggestion source type, e.g. market, category."},"frequency":{"type":"integer","description":"May be an aggregated count, or always 1, depending on how the suggestion was generated."}}},"SearchSuggestResponse":{"type":"object","required":["suggestions","meta"],"properties":{"suggestions":{"type":"array","items":{"$ref":"#/components/schemas/SearchSuggestion"}},"meta":{"type":"object","properties":{"query":{"type":"string"},"returned":{"type":"integer"}}}}},"ScreenerMarket":{"type":"object","description":"One screener hit — the market, and the outcome the screen matched on. Prices and spread are cents; money is dollars.","required":["id","marketId","provider"],"properties":{"id":{"type":"string","description":"The id this market's book is published under. For Polymarket that is the venue condition id, not the market id search returns — see `marketId`."},"marketId":{"type":"string","nullable":true,"description":"The id to address this market by everywhere else — the terminal, the market-data websocket, the trades API. Always present, and equal to `id` on every venue keyed by its market id. Null only on a Polymarket row the metadata cache could not resolve; a null row cannot be opened, and `id` is not a substitute for it."},"provider":{"type":"string"},"title":{"type":"string","nullable":true,"description":"Null when the discover cache has never seen this market, alongside `hasMetadata` false. Presenting that — usually as the id — is the client's call."},"eventTitle":{"type":"string","nullable":true},"image":{"type":"string","nullable":true},"icon":{"type":"string","nullable":true},"category":{"type":"string","nullable":true},"status":{"type":"string","nullable":true},"hasMetadata":{"type":"boolean","description":"False when only live numbers are known and the title is the id."},"outcomeIndex":{"type":"integer","nullable":true,"description":"The outcome the quote figures below describe."},"matchedLeg":{"type":"boolean","description":"True when the screen picked this outcome. False when the screen was contract-level only and this is just the primary outcome."},"outcomeName":{"type":"string","nullable":true},"bid":{"type":"number","nullable":true,"description":"Cents."},"ask":{"type":"number","nullable":true,"description":"Cents."},"mid":{"type":"number","nullable":true,"description":"Cents."},"spread":{"type":"number","nullable":true,"description":"Cents wide on this outcome; null when it is not quoting both sides."},"price":{"type":"number","description":"Cents; the record's headline price."},"volume24h":{"type":"number","description":"Dollars."},"volume1h":{"type":"number","description":"Dollars."},"liquidity":{"type":"number","description":"Dollars resting across every outcome, both sides."},"outcomeLiquidity":{"type":"number","nullable":true,"description":"Dollars resting on this outcome alone, both sides."},"bookAgeSeconds":{"type":"number","nullable":true,"description":"Seconds since this leg's top of book was published; null when it never has been. A large value means nothing is streaming this market."},"openInterest":{"type":"number","description":"Contracts."},"openInterestNotional":{"type":"number","description":"Dollars."},"updatedAt":{"type":"string","format":"date-time"}}},"ScreenerResponse":{"type":"object","required":["markets","total","available"],"properties":{"markets":{"type":"array","items":{"$ref":"#/components/schemas/ScreenerMarket"}},"total":{"type":"integer","description":"Matches before paging. **-1** means the ordered walk stopped at the page bound and the rest were not counted — render it as \"top N\", not as a count."},"available":{"type":"boolean","description":"False when the live vitals service could not be reached. Distinct from a screen that matched nothing, which is available with an empty `markets`."},"plan":{"type":"string","description":"How the scan ran (`index_ordered`, `index_range`, `scan`). Diagnostic."},"limit":{"type":"integer"},"offset":{"type":"integer"}}},"ScreenerPreset":{"type":"object","required":["id","label","description","params"],"properties":{"id":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"params":{"type":"object","description":"Query parameters for /search/screener, including `sort`.","additionalProperties":true}}},"ScreenerPresetsResponse":{"type":"object","required":["presets","sorts"],"properties":{"presets":{"type":"array","items":{"$ref":"#/components/schemas/ScreenerPreset"}},"sorts":{"type":"array","items":{"type":"string"}}}},"DiscoverMarketOutcome":{"type":"object","description":"One outcome market inside a grouped (isGrouped=true) DiscoverMarket, as returned by /api/markets/discover/v2.","properties":{"id":{"type":"string"},"ticker":{"type":"string"},"title":{"type":"string"},"outcomeLabel":{"type":"string"},"price":{"type":"number","description":"Cents/100."},"volume":{"type":"number"},"volumeTotal":{"type":"number","description":"Same value as volume."},"volume24h":{"type":"number"},"volume1h":{"type":"number","description":"Dollars traded in the last hour, from our trade tape (or the venue's own hourly figure where it publishes one). 0 where neither reports an hour — that is \"not measured\", not \"no trades\". Rank on volume1hRank instead."},"volume1hRank":{"type":"number","description":"Ranking score for the hourly dimension — the hour above where it is known, the 24h volume spread over its hours where it is not. Never display it."},"token_id":{"type":"string","nullable":true},"condition_id":{"type":"string","nullable":true},"rewardRate":{"type":"number"}}},"DiscoverMarket":{"type":"object","description":"One row of /api/markets/discover/v2's `markets` array. When isGrouped=true this represents an event with its outcomes nested (eventTitle + outcomes populated, outcomeLabel/yes_sub_title/ no_sub_title/token_id/condition_id absent at the top level); when isGrouped=false it's a single standalone market (outcomeLabel/ yes_sub_title/no_sub_title/token_id/condition_id populated, eventTitle/outcomes absent).","required":["id","ticker","title","price","provider","status","isGrouped"],"properties":{"id":{"type":"string"},"ticker":{"type":"string"},"event_id":{"type":"string","nullable":true},"eventTitle":{"type":"string","description":"Grouped rows only."},"title":{"type":"string"},"outcomeLabel":{"type":"string","nullable":true,"description":"Single (isGrouped=false) rows only."},"yes_sub_title":{"type":"string","nullable":true,"description":"Single rows only."},"no_sub_title":{"type":"string","nullable":true,"description":"Single rows only."},"price":{"type":"number","description":"Cents/100."},"volume":{"type":"number"},"volume24h":{"type":"number"},"volume1h":{"type":"number","description":"Dollars traded in the last hour, from our trade tape (or the venue's own hourly figure where it publishes one). 0 where neither reports an hour — that is \"not measured\", not \"no trades\". Rank on volume1hRank instead."},"volume1hRank":{"type":"number","description":"Ranking score for the hourly dimension — the hour above where it is known, the 24h volume spread over its hours where it is not. Never display it."},"liquidity":{"type":"number"},"rewardRate":{"type":"number"},"provider":{"type":"string"},"status":{"type":"string"},"expiration":{"type":"string","format":"date-time","nullable":true},"token_id":{"type":"string","nullable":true,"description":"Single rows only."},"condition_id":{"type":"string","nullable":true,"description":"Single rows only."},"category":{"type":"string","nullable":true},"image":{"type":"string","nullable":true},"icon":{"type":"string","nullable":true},"isGrouped":{"type":"boolean"},"outcomes":{"type":"array","description":"Grouped rows only.","items":{"$ref":"#/components/schemas/DiscoverMarketOutcome"}}}},"DiscoverV2Response":{"type":"object","required":["markets","total","offset","limit","timestamp"],"properties":{"markets":{"type":"array","items":{"$ref":"#/components/schemas/DiscoverMarket"}},"total":{"type":"integer"},"offset":{"type":"integer"},"limit":{"type":"integer"},"timestamp":{"type":"string","description":"ISO timestamp of the underlying cache build; empty string when the response falls back to a live query, or on the empty-filter short-circuit path."}}},"DiscoverSearchIndexOutcome":{"type":"object","description":"One outcome market inside a grouped search-index entry. Distinct field set from DiscoverMarketOutcome (no `volume`/`condition_id`; adds `token_id`).","properties":{"id":{"type":"string"},"title":{"type":"string"},"ticker":{"type":"string"},"price":{"type":"number"},"volume1h":{"type":"number"},"volume24h":{"type":"number"},"volumeTotal":{"type":"number"},"outcomeLabel":{"type":"string"},"token_id":{"type":"string","nullable":true},"rewardRate":{"type":"number"}}},"DiscoverSearchIndexMarket":{"type":"object","description":"One row of /api/markets/discover/v2/search-index's `markets` array. Same grouped/single split as DiscoverMarket but this shape omits `status`/`event_id` and always includes `token_id` at the top level.","required":["id","ticker","title","price","provider","isGrouped"],"properties":{"id":{"type":"string"},"ticker":{"type":"string"},"title":{"type":"string"},"eventTitle":{"type":"string","description":"Grouped rows only."},"yes_sub_title":{"type":"string","nullable":true,"description":"Single rows only."},"no_sub_title":{"type":"string","nullable":true,"description":"Single rows only."},"price":{"type":"number"},"volume":{"type":"number"},"volume24h":{"type":"number"},"volume1h":{"type":"number"},"liquidity":{"type":"number"},"rewardRate":{"type":"number"},"provider":{"type":"string"},"expiration":{"type":"string","format":"date-time","nullable":true},"category":{"type":"string","nullable":true},"image":{"type":"string","nullable":true},"icon":{"type":"string","nullable":true},"isGrouped":{"type":"boolean"},"token_id":{"type":"string","nullable":true},"outcomes":{"type":"array","description":"Grouped rows only.","items":{"$ref":"#/components/schemas/DiscoverSearchIndexOutcome"}}}},"DiscoverSearchIndexResponse":{"type":"object","required":["markets","total","timestamp"],"properties":{"markets":{"type":"array","items":{"$ref":"#/components/schemas/DiscoverSearchIndexMarket"}},"total":{"type":"integer"},"timestamp":{"type":"string"}}},"DiscoverTickerMarket":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"provider":{"type":"string"},"price":{"type":"number"},"volume1h":{"type":"number"},"token_id":{"type":"string","nullable":true}}},"DiscoverTickerResponse":{"type":"object","required":["markets","count","timestamp"],"properties":{"markets":{"type":"array","items":{"$ref":"#/components/schemas/DiscoverTickerMarket"}},"count":{"type":"integer"},"timestamp":{"type":"string"}}},"DiscoverBreakingMarket":{"type":"object","properties":{"id":{"type":"string"},"ticker":{"type":"string"},"title":{"type":"string"},"provider":{"type":"string"},"price":{"type":"number"},"volume1h":{"type":"number"},"priceChange24hSigned":{"type":"number","description":"Signed percentage/point change over 24h."},"token_id":{"type":"string","nullable":true},"condition_id":{"type":"string","nullable":true},"image":{"type":"string","nullable":true},"icon":{"type":"string","nullable":true},"category":{"type":"string","nullable":true}}},"DiscoverBreakingResponse":{"type":"object","required":["markets","count","timestamp"],"properties":{"markets":{"type":"array","items":{"$ref":"#/components/schemas/DiscoverBreakingMarket"}},"count":{"type":"integer"},"timestamp":{"type":"string"}}},"DiscoverExpiringMarket":{"type":"object","properties":{"id":{"type":"string"},"ticker":{"type":"string"},"title":{"type":"string"},"provider":{"type":"string"},"price":{"type":"number"},"volume1h":{"type":"number"},"volume24h":{"type":"number"},"volume":{"type":"number"},"expiration":{"type":"string","format":"date-time"},"token_id":{"type":"string","nullable":true},"condition_id":{"type":"string","nullable":true},"category":{"type":"string","nullable":true},"image":{"type":"string","nullable":true},"icon":{"type":"string","nullable":true},"isGrouped":{"type":"boolean","enum":[false]}}},"DiscoverExpiringResponse":{"type":"object","required":["markets","count","timestamp"],"properties":{"markets":{"type":"array","items":{"$ref":"#/components/schemas/DiscoverExpiringMarket"}},"count":{"type":"integer"},"timestamp":{"type":"string"}}},"DiscoverSubcategory":{"type":"object","required":["name","slug","count"],"properties":{"name":{"type":"string"},"slug":{"type":"string"},"count":{"type":"integer","description":"Approximate active-market count (topic ∩ subtopic ∩ active-ranked-set); may run slightly higher than the post-grouping total shown on the cards page."}}},"DiscoverSubcategoriesResponse":{"type":"object","required":["category","subcategories"],"properties":{"category":{"type":"string"},"subcategories":{"type":"array","items":{"$ref":"#/components/schemas/DiscoverSubcategory"},"description":"Sorted by count descending; zero-count subtopics are omitted."}}},"DiscoverKalshiSportsEvent":{"type":"object","description":"A cached Kalshi sports event object written by a background job. Only the fields this router reads are typed; the object may carry additional passthrough fields.","properties":{"event_ticker":{"type":"string"},"total_volume":{"type":"number"},"market_count":{"type":"integer"}},"additionalProperties":true},"DiscoverKalshiMatchedGame":{"type":"object","required":["away","home","league","kalshi_events","total_volume","total_markets"],"properties":{"away":{"type":"string","description":"Uppercased team code."},"home":{"type":"string","description":"Uppercased team code."},"league":{"type":"string","description":"Lowercased league code","or empty string if not supplied.":null},"kalshi_events":{"type":"array","items":{"$ref":"#/components/schemas/DiscoverKalshiSportsEvent"},"description":"Sorted by total_volume descending."},"total_volume":{"type":"number"},"total_markets":{"type":"integer"}}},"DiscoverKalshiLiveResponse":{"type":"object","required":["events","matched"],"properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/DiscoverKalshiSportsEvent"},"description":"All cached events (unfiltered)."},"matched":{"type":"array","items":{"$ref":"#/components/schemas/DiscoverKalshiMatchedGame"},"description":"Empty unless `games` was supplied and matched at least one event."}}},"DiscoverTrendingMarket":{"type":"object","properties":{"id":{"type":"string"},"ticker":{"type":"string","description":"Same value as id."},"title":{"type":"string"},"price":{"type":"number"},"volume":{"type":"number","description":"Total volume (TrendingMarket.volume_total)."},"volume1h":{"type":"number"},"provider":{"type":"string"},"outcomeLabel":{"type":"string","nullable":true},"event_id":{"type":"string","nullable":true}}},"DiscoverTrendingResponse":{"type":"object","required":["markets","count","timestamp"],"properties":{"markets":{"type":"array","items":{"$ref":"#/components/schemas/DiscoverTrendingMarket"}},"count":{"type":"integer"},"timestamp":{"type":"string"}}},"ProvidersConfig":{"type":"object","description":"Exchange-agnostic provider configuration.","required":["id","display_name","icon_url","chain_id","chain_name","is_active","supported_order_types","supports_walk_the_book","supports_token_approval","has_multi_token_markets","auth_flow_type"],"properties":{"id":{"type":"string","example":"kalshi","description":"'kalshi' | 'polymarket' | 'predictfun' | 'hyperliquid' | ..."},"display_name":{"type":"string","example":"Kalshi"},"icon_url":{"type":"string"},"chain_id":{"type":"string"},"chain_name":{"type":"string"},"is_active":{"type":"boolean"},"supported_order_types":{"type":"array","items":{"type":"string"},"example":["limit","market"]},"supports_walk_the_book":{"type":"boolean"},"supports_token_approval":{"type":"boolean"},"has_multi_token_markets":{"type":"boolean"},"auth_flow_type":{"type":"string","enum":["none","wallet_signature","api_key"]},"token_id_format":{"type":"string","enum":["opaque","clob_uint256"],"default":"opaque"},"metadata_key_type":{"type":"string","enum":["marketId","conditionId"],"default":"marketId"},"numeric_id":{"type":"integer","nullable":true},"primary_color":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time","nullable":true},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"ProvidersConfigsResponse":{"type":"object","required":["configs","meta"],"properties":{"configs":{"type":"array","items":{"$ref":"#/components/schemas/ProvidersConfig"}},"meta":{"type":"object","required":["count"],"properties":{"count":{"type":"integer"}}}}},"ProvidersAPIAccessResponse":{"type":"object","required":["providers","meta"],"properties":{"providers":{"type":"array","description":"Active provider ids whose API-key access is enabled.","items":{"type":"string"},"example":["hyperliquid","kalshi","polymarket"]},"meta":{"type":"object","required":["count"],"properties":{"count":{"type":"integer","example":3}}}}},"SportsTrendingMatchedResponse":{"type":"object","description":"Top matched sports markets ranked by live Kalshi volume.","required":["matches","count"],"properties":{"matches":{"type":"array","items":{"$ref":"#/components/schemas/SportsTrendingMatch"}},"count":{"type":"integer","description":"Number of items in matches (after truncation to limit).","example":5}}},"SportsTrendingMatch":{"type":"object","required":["slug","title","image","icon","volume1h","polymarket","kalshi"],"properties":{"slug":{"type":"string","description":"Game slug, used as the matching-cache key.","example":"nba-lal-bos-2026-01-15"},"title":{"type":"string","description":"Human-readable game title, from the matching cache.","example":"Lakers vs Celtics"},"image":{"type":["string","null"],"description":"Always null — not populated by this endpoint."},"icon":{"type":["string","null"],"description":"Always null — not populated by this endpoint."},"volume1h":{"type":"number","format":"double","description":"Kalshi 1h (or total, as fallback) volume used for ranking, from the discover cache.","example":48213.5},"polymarket":{"$ref":"#/components/schemas/SportsTrendingMatchPolymarketSide"},"kalshi":{"$ref":"#/components/schemas/SportsTrendingMatchKalshiSide"}}},"SportsTrendingMatchPolymarketSide":{"type":"object","required":["marketId","tokenId","price"],"properties":{"marketId":{"type":"string","description":"Equal to the game slug (Polymarket has no separate market id in this cache entry).","example":"nba-lal-bos-2026-01-15"},"tokenId":{"type":["string","null"],"description":"Polymarket CLOB token id, from the matching cache.","example":"10897234..."},"price":{"type":["number","null"],"format":"double","example":0.62}}},"SportsTrendingMatchKalshiSide":{"type":"object","required":["marketId","eventTicker","price"],"properties":{"marketId":{"type":["string","null"],"example":"KXNBAGAME-26JAN15LALBOS-LAL"},"eventTicker":{"type":["string","null"],"example":"KXNBAGAME-26JAN15LALBOS"},"price":{"type":["number","null"],"format":"double","example":0.6}}},"SportsMatchingMarketsResponse":{"type":"object","description":"Map keyed by requested game slug. Slugs with no cache entry, or an unparseable cache value, are omitted from this object entirely.","additionalProperties":{"$ref":"#/components/schemas/SportsMatchingMarketEntry"},"example":{"nba-lal-bos-2026-01-15":{"title":"Lakers vs Celtics","tokenId":"10897234...","providers":[{"provider":"polymarket","marketId":"nba-lal-bos-2026-01-15","price":0.62},{"provider":"kalshi","marketId":"KXNBAGAME-26JAN15LALBOS-LAL","price":0.6,"eventTicker":"KXNBAGAME-26JAN15LALBOS"},{"provider":"predictfun","marketId":"39142","price":0.61}]}}},"SportsMatchingMarketEntry":{"type":"object","description":"Raw contents of a cached cross-venue sports market match entry.","required":["providers"],"properties":{"title":{"type":"string","example":"Lakers vs Celtics"},"tokenId":{"type":"string","description":"Polymarket CLOB token id for the primary market side.","example":"10897234..."},"providers":{"type":"array","items":{"$ref":"#/components/schemas/SportsMatchingProvider"}}}},"SportsMatchingProvider":{"type":"object","required":["provider","marketId","price"],"properties":{"provider":{"type":"string","enum":["polymarket","kalshi","predictfun"]},"marketId":{"type":"string","example":"KXNBAGAME-26JAN15LALBOS-LAL"},"price":{"type":"number","format":"double","nullable":true,"description":"0-1 scale probability/price.","example":0.6},"eventTicker":{"type":"string","nullable":true,"description":"Kalshi event ticker. Present only on kalshi provider entries.","example":"KXNBAGAME-26JAN15LALBOS"},"volume":{"type":"number","format":"double","nullable":true},"teamPrices":{"type":"array","description":"Per-team YES markets for providers that split 3-way moneylines.","items":{"type":"object","required":["name","marketId","price"],"properties":{"name":{"type":"string"},"marketId":{"type":"string"},"price":{"type":"number","format":"double","nullable":true}}}},"drawMarketId":{"type":"string","nullable":true},"drawPrice":{"type":"number","format":"double","nullable":true},"homeMarketId":{"type":"string","nullable":true},"homePrice":{"type":"number","format":"double","nullable":true},"awayMarketId":{"type":"string","nullable":true},"awayPrice":{"type":"number","format":"double","nullable":true}}},"SportsKalshiFiltersResponse":{"type":"object","description":"Verbatim pass-through of the Kalshi filters_by_sport upstream payload.","required":["filters_by_sports","sport_ordering"],"properties":{"filters_by_sports":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/SportsKalshiFilterSport"},"example":{"Basketball":{"competitions":{"NBA":{"scopes":["Game","Series","Season"]}},"scopes":["Game","Series","Season"]}}},"sport_ordering":{"type":"array","items":{"type":"string"},"example":["All sports","Basketball","Football","Baseball"]}}},"SportsKalshiFilterSport":{"type":"object","required":["competitions","scopes"],"properties":{"competitions":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/SportsKalshiFilterCompetition"}},"scopes":{"type":"array","items":{"type":"string"}}}},"SportsKalshiFilterCompetition":{"type":"object","required":["scopes"],"properties":{"scopes":{"type":"array","items":{"type":"string"},"example":["Game","Series","Season"]}}},"SportsLiveEventsResponse":{"type":"object","required":["events","matchingMarkets"],"properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/SportsLiveEvent"}},"matchingMarkets":{"type":"object","description":"Map keyed by matched slug, one entry per event that resolved a matchingSlug.","additionalProperties":{"$ref":"#/components/schemas/SportsMatchingMarketEntry"}}}},"SportsLiveEvent":{"type":"object","required":["gameId","sport","sportFamily","homeTeam","awayTeam","homeScore","awayScore","status","period","elapsed","live","ended","slug","matchingSlug","ts","urgencyScore","stale","primaryMarket","marketsCount","timing"],"properties":{"timing":{"$ref":"#/components/schemas/SportsTiming"},"gameId":{"type":"string","example":"12345678"},"sport":{"type":"string","description":"Lowercased league abbreviation.","example":"nba"},"sportFamily":{"type":"string","description":"Canonical category id from the sports catalog.","example":"basketball"},"homeTeam":{"type":"string","example":"Boston Celtics"},"awayTeam":{"type":"string","example":"Los Angeles Lakers"},"homeScore":{"type":"integer","description":"Series score if the game has one, else the game score.","example":88},"awayScore":{"type":"integer","example":91},"status":{"type":"string","example":"live"},"period":{"type":["string","null"],"example":"Q4"},"elapsed":{"description":"Opaque provider clock string; direction, period resets, and overtime semantics are not verified.","nullable":true,"example":"02:14"},"live":{"type":"boolean"},"ended":{"type":"boolean"},"slug":{"type":["string","null"],"example":"nba-lal-bos-2026-01-15"},"matchingSlug":{"type":["string","null"],"description":"Slug used as the key into matchingMarkets.","example":"nba-lal-bos-2026-01-15"},"ts":{"type":"string","format":"date-time","description":"Game-state observation timestamp; source observation time when available, otherwise time received by Kairos.","example":"2026-07-22T23:14:05Z"},"urgencyScore":{"type":"integer","description":"Candidate-ranking heuristic for fresh games explicitly in play. Zero for stale, stopped, ended, unknown-status, and schedule-only games. Not a remaining-time signal.","example":160},"stale":{"type":"boolean","description":"True for state older than 120 seconds, invalid or future timestamps, timestamps without a timezone, or absence of a live score feed. Evaluated at timing.evaluatedAt."},"primaryMarket":{"oneOf":[{"$ref":"#/components/schemas/SportsNormalizedMarket"},{"type":"null"}]},"marketsCount":{"type":"integer","example":6},"markets":{"type":"array","description":"Present only when include_markets=true.","items":{"$ref":"#/components/schemas/SportsNormalizedMarket"}}}},"SportsTiming":{"type":"object","description":"Conservative timing evidence. Polymarket elapsed is not normalized to remaining time. Predict.fun schedule windows do not establish live score freshness. Consumers must age stateAgeSeconds from evaluatedAt because responses can be cached.","required":["source","status","clock","clockDirection","clockRunning","periodRemainingSeconds","gameRemainingSeconds","overtime","stateAgeSeconds","evaluatedAt","staleAfterSeconds","stale","usableForLateGame","unavailableReason"],"properties":{"source":{"type":"string","enum":["polymarket","predictfun"]},"status":{"type":"string","enum":["scheduled","live","break","overtime","shootout","suspended","delayed","postponed","cancelled","final","unknown"]},"clock":{"type":["string","null"]},"clockDirection":{"type":"string","enum":["unknown"]},"clockRunning":{"type":["boolean","null"],"description":"Currently null; running state is not established."},"periodRemainingSeconds":{"type":["number","null"],"description":"Currently null; do not derive this from elapsed without verified clock semantics."},"gameRemainingSeconds":{"type":["number","null"],"description":"Currently null; overtime and eventual completion cannot be inferred from regulation time."},"overtime":{"type":["boolean","null"],"description":"True for explicit OT or extra-time evidence, false for regulation final FT, otherwise null. Completion after overtime is not active overtime."},"stateAgeSeconds":{"type":["number","null"],"minimum":0},"evaluatedAt":{"type":"string","format":"date-time"},"staleAfterSeconds":{"type":"integer","const":120},"stale":{"type":"boolean"},"usableForLateGame":{"type":"boolean","const":false,"description":"No authoritative countdown is currently available from these sources."},"unavailableReason":{"type":"string","enum":["unknown_clock_semantics","missing_clock","game_not_in_play","stale_state","invalid_timestamp","no_live_score_feed"]}}},"SportsNormalizedMarket":{"type":"object","description":"Upstream market normalized into a common shape used across the live-events, event-markets, and upcoming-events endpoints.","required":["id","question","slug","conditionId","tokenId","outcomePrices","outcomes","volumeNum","liquidityNum","acceptingOrders","sportsMarketType","groupItemTitle","bestBid","bestAsk","spread","provider","image","icon"],"properties":{"id":{"type":"string","example":"587234"},"question":{"type":"string","example":"Will the Celtics win?"},"slug":{"type":"string","example":"nba-lal-bos-2026-01-15-bos"},"conditionId":{"type":"string","example":"0xabc123..."},"tokenId":{"type":"string","description":"First clobTokenId for the market.","example":"10897234..."},"outcomePrices":{"type":"array","items":{"type":"number","format":"double"},"example":[0.6,0.4]},"outcomes":{"type":"array","items":{"type":"string"},"example":["Yes","No"]},"volumeNum":{"type":"number","format":"double","example":128432.1},"liquidityNum":{"type":"number","format":"double","example":42311},"acceptingOrders":{"type":"boolean"},"sportsMarketType":{"type":["string","null"],"example":"moneyline"},"groupItemTitle":{"type":["string","null"],"example":"Celtics"},"gameStartTime":{"type":["string","null"],"format":"date-time","description":"Scheduled fixture kickoff used for cross-provider live-game matching."},"bestBid":{"type":["number","null"],"format":"double","example":0.59},"bestAsk":{"type":["number","null"],"format":"double","example":0.61},"spread":{"type":["number","null"],"format":"double","example":0.02},"provider":{"type":"string","example":"polymarket"},"image":{"type":["string","null"]},"icon":{"type":["string","null"]},"outcomeMarketIds":{"type":"array","description":"Only present on 3-way (soccer moneyline) composite markets. Length 3, [home, draw, away].","items":{"type":["string","null"]},"example":["587234","587235","587236"]},"outcomeTokenIds":{"type":"array","description":"Per-outcome token IDs in the same order as outcomes, when available.","items":{"type":["string","null"]}},"outcomeConditionIds":{"type":"array","description":"Only present on 3-way composite markets.","items":{"type":["string","null"]}}}},"SportsEventMarketsResponse":{"type":"object","required":["gameId","markets"],"properties":{"gameId":{"type":"string","description":"Resolved gameId, or the raw game_id/slug query value (\"unknown\" if neither resolved).","example":"12345678"},"markets":{"type":"array","items":{"$ref":"#/components/schemas/SportsNormalizedMarket"}}}},"SportsKalshiLiveGamesResponse":{"type":"object","required":["games"],"properties":{"games":{"type":"array","items":{"$ref":"#/components/schemas/SportsKalshiLiveGame"}}}},"SportsKalshiLiveGame":{"type":"object","required":["milestoneId","type","family","league","sport","title","startDate","homeTeamId","awayTeamId","home","away","live","moneyline","spread","total","featuredImageUrl","totalVolume"],"properties":{"milestoneId":{"type":"string","example":"MILESTONE-987"},"type":{"type":"string","enum":["hockey_tournament","basketball_game","baseball_game","football_game","soccer_tournament_multi_leg","esports_match"]},"family":{"type":"string","enum":["team","soccer","esports"]},"league":{"type":"string","example":"NBA"},"sport":{"type":"string","example":"basketball"},"title":{"type":"string","example":"Lakers at Celtics"},"startDate":{"type":"string","format":"date-time"},"homeTeamId":{"type":"string"},"awayTeamId":{"type":"string"},"home":{"$ref":"#/components/schemas/SportsKalshiTeamBlock"},"away":{"$ref":"#/components/schemas/SportsKalshiTeamBlock"},"draw":{"oneOf":[{"$ref":"#/components/schemas/SportsKalshiTeamBlock"},{"type":"null"}],"description":"Only meaningful for family=soccer."},"drawTicker":{"type":["string","null"],"description":"Only meaningful for family=soccer."},"drawPrice":{"type":["number","null"],"format":"double","description":"Only meaningful for family=soccer."},"live":{"$ref":"#/components/schemas/SportsKalshiLiveData"},"moneyline":{"$ref":"#/components/schemas/SportsKalshiMoneyline"},"spread":{"oneOf":[{"$ref":"#/components/schemas/SportsKalshiLinesGroup"},{"type":"null"}]},"total":{"oneOf":[{"$ref":"#/components/schemas/SportsKalshiLinesGroup"},{"type":"null"}]},"featuredImageUrl":{"type":["string","null"]},"totalVolume":{"type":"number","format":"double","description":"Sum of moneyline market volumes.","example":84213}}},"SportsKalshiTeamBlock":{"type":"object","required":["name","logoUrl","color"],"properties":{"name":{"type":"string","example":"Boston Celtics"},"logoUrl":{"type":["string","null"]},"color":{"type":["string","null"],"example":"#007A33"}}},"SportsKalshiLiveData":{"type":"object","description":"Live game-state block. Always includes homePoints, awayPoints, lastPlay, status. The remaining fields vary by milestone type: soccer_tournament_multi_leg adds statusText/half/time; esports_match adds format/currentMap; hockey_tournament adds period/periodRemaining; basketball_game adds period/periodType/periodRemaining/possession; baseball_game adds inning/inningHalf/balls/strikes/outs/bases; football_game adds quarter/clock/down/yardsToFirst/yardline/possessionTeamId.","additionalProperties":true,"properties":{"homePoints":{"type":"integer"},"awayPoints":{"type":"integer"},"lastPlay":{"type":"string"},"status":{"type":"string"}},"example":{"homePoints":88,"awayPoints":91,"lastPlay":"3-pointer made","status":"in_progress","period":4,"periodType":"quarter","periodRemaining":"02:14","possession":"away"}},"SportsKalshiMoneyline":{"type":"object","required":["eventTicker","seriesTicker","homeTicker","awayTicker","homePrice","awayPrice","homeYesAsk","awayYesAsk"],"properties":{"eventTicker":{"type":"string","example":"KXNBAGAME-26JAN15LALBOS"},"seriesTicker":{"type":"string","example":"KXNBAGAME"},"homeTicker":{"type":"string","example":"KXNBAGAME-26JAN15LALBOS-BOS"},"awayTicker":{"type":"string","example":"KXNBAGAME-26JAN15LALBOS-LAL"},"homePrice":{"type":["number","null"],"format":"double","description":"yes_ask, falling back to last_price.","example":0.6},"awayPrice":{"type":["number","null"],"format":"double","example":0.41},"homeYesAsk":{"type":["number","null"],"format":"double"},"awayYesAsk":{"type":["number","null"],"format":"double"}}},"SportsKalshiLinesGroup":{"type":"object","required":["eventTicker","seriesTicker","markets"],"properties":{"eventTicker":{"type":"string"},"seriesTicker":{"type":"string"},"markets":{"type":"array","items":{"$ref":"#/components/schemas/SportsKalshiLineMarket"}}}},"SportsKalshiLineMarket":{"type":"object","required":["ticker","yesSubTitle","noSubTitle","lastPrice","yesAsk","noAsk","volume"],"properties":{"ticker":{"type":"string","example":"KXNBAGAME-26JAN15LALBOS-T220.5"},"yesSubTitle":{"type":"string","example":"Over 220.5"},"noSubTitle":{"type":"string","example":"Under 220.5"},"lastPrice":{"type":["number","null"],"format":"double"},"yesAsk":{"type":["number","null"],"format":"double"},"noAsk":{"type":["number","null"],"format":"double"},"volume":{"type":["number","null"],"format":"double"}}},"SportsPolyKalshiPairingsResponse":{"type":"object","required":["pairings"],"properties":{"pairings":{"type":"array","items":{"$ref":"#/components/schemas/SportsPolyKalshiPairing"}}}},"SportsPolyKalshiPairing":{"type":"object","required":["polyGameId","polySport","kalshiEventTicker","kalshiLeague","kalshiMilestoneId","kalshiMarkets"],"properties":{"polyGameId":{"type":"string","example":"10078222"},"polySport":{"type":"string","example":"mlb"},"kalshiEventTicker":{"type":"string","example":"KXMLBGAME-26JUN041410SFMIL"},"kalshiLeague":{"type":"string","example":"MLB"},"kalshiMilestoneId":{"type":"string","example":"MILESTONE-4521"},"kalshiMarkets":{"type":"array","items":{"$ref":"#/components/schemas/SportsKalshiFlattenedMarket"}}}},"SportsKalshiFlattenedMarket":{"type":"object","required":["ticker","title","yes_sub_title","price","volume","image","eventTicker"],"properties":{"ticker":{"type":"string","example":"KXMLBGAME-26JUN041410SFMIL-SF"},"title":{"type":"string","example":"Giants at Brewers"},"yes_sub_title":{"type":"string","example":"Giants win"},"price":{"type":"number","format":"double","description":"Rescaled to a 0-100 range (not the 0-1 scale used elsewhere in this API), matching a legacy response shape.","example":46.5},"volume":{"type":"number","format":"double","example":12045},"image":{"type":["string","null"]},"eventTicker":{"type":"string","example":"KXMLBGAME-26JUN041410SFMIL"}}},"SportsCatalogResponse":{"type":"object","required":["categories"],"properties":{"categories":{"type":"array","items":{"$ref":"#/components/schemas/SportsCatalogCategory"}}}},"SportsCatalogCategory":{"type":"object","required":["id","label","leagues"],"properties":{"id":{"type":"string","example":"basketball"},"label":{"type":"string","example":"Basketball"},"leagues":{"type":"array","items":{"$ref":"#/components/schemas/SportsCatalogLeague"}}}},"SportsCatalogLeague":{"type":"object","required":["slug","label","tagId","seriesId"],"properties":{"slug":{"type":"string","example":"nba"},"label":{"type":"string","example":"NBA"},"tagId":{"type":"integer","description":"Hardcoded constant shared by every league entry.","example":100639},"seriesId":{"type":["integer","null"],"description":"Resolved Polymarket series id, or null if unresolved.","example":123}}},"SportsUpcomingEventsResponse":{"type":"object","required":["events","startingEvents","matchingMarkets"],"properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/SportsUpcomingEvent"}},"startingEvents":{"type":"array","description":"Fixtures whose scheduled start was within the last ten minutes but which have not yet appeared in the live feed. These are schedule-derived, not confirmed live.","items":{"$ref":"#/components/schemas/SportsUpcomingEvent"}},"matchingMarkets":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/SportsUpcomingMatchingEntry"}}}},"SportsUpcomingEvent":{"type":"object","required":["eventId","title","startTime","sport","sportFamily","league","teamA","teamB","image","primaryMarket","marketsCount","eventSlug","drawPrice","teamBPrice"],"properties":{"eventId":{"type":"string","example":"10078222"},"title":{"type":"string","example":"Lakers at Celtics"},"startTime":{"type":"string","format":"date-time","description":"Normalized ISO8601 gameStartTime (endDate fallback for events missing it)."},"sport":{"type":"string","description":"Broad sport family used for display and grouping.","example":"soccer"},"sportFamily":{"type":"string","description":"Canonical category id from the sports catalog; equal to sport on this route.","example":"soccer"},"league":{"type":"string","description":"Sports-catalog league slug.","example":"uel"},"teamA":{"type":["string","null"],"example":"Los Angeles Lakers"},"teamB":{"type":["string","null"],"example":"Boston Celtics"},"image":{"type":["string","null"]},"primaryMarket":{"oneOf":[{"$ref":"#/components/schemas/SportsUpcomingMarket"},{"type":"null"}]},"marketsCount":{"type":"integer","example":4},"eventSlug":{"type":["string","null"],"description":"Venue fixture slug accepted by the cross-venue game-markets catalog.","example":"nba-lal-bos-2026-01-15"},"matchingSlug":{"type":["string","null"],"description":"Alternate market slug under which cross-venue matches may be published."},"startingEligible":{"type":"boolean","description":"True when a known kickoff lets the fixture enter the starting grace window."},"awaitingLive":{"type":"boolean","description":"True only in startingEvents; it does not confirm that the game is live."},"drawPrice":{"type":["number","null"],"format":"double","description":"Present for soccer 3-way events."},"teamBPrice":{"type":["number","null"],"format":"double","description":"Present for soccer 3-way events."}}},"SportsUpcomingMarket":{"type":"object","required":["id","provider","question","slug","conditionId","tokenId","outcomePrices","outcomes","volumeNum","liquidityNum","acceptingOrders","sportsMarketType","gameId"],"properties":{"id":{"type":"string"},"provider":{"type":"string","enum":["polymarket","kalshi","predictfun"]},"question":{"type":"string"},"slug":{"type":"string"},"conditionId":{"type":"string"},"tokenId":{"type":"string"},"outcomePrices":{"type":"array","items":{"type":"number","format":"double"}},"outcomes":{"type":"array","items":{"type":"string"}},"volumeNum":{"type":"number","format":"double"},"liquidityNum":{"type":"number","format":"double"},"acceptingOrders":{"type":"boolean"},"sportsMarketType":{"type":["string","null"]},"gameId":{"description":"Raw upstream game id, type varies by provider.","nullable":true},"outcomeMarketIds":{"type":"array","description":"Only present on 3-way (soccer) composite markets. [home, draw, away].","items":{"type":["string","null"]}},"outcomeTokenIds":{"type":"array","description":"Only present on 3-way composite markets.","items":{"type":["string","null"]}},"outcomeConditionIds":{"type":"array","description":"Only present on 3-way composite markets.","items":{"type":["string","null"]}}}},"SportsUpcomingMatchingEntry":{"type":"object","required":["providers"],"properties":{"providers":{"type":"array","items":{"$ref":"#/components/schemas/SportsUpcomingMatchingProvider"}}}},"SportsUpcomingMatchingProvider":{"type":"object","required":["provider","price","marketId","eventTicker","volume"],"properties":{"provider":{"type":"string","enum":["polymarket","kalshi","predictfun","hyperliquid"]},"price":{"type":["number","null"],"format":"double"},"marketId":{"type":"string"},"eventTicker":{"type":["string","null"]},"volume":{"type":["number","null"],"format":"double"},"source":{"type":"string","description":"Present as `market_matcher` when cross-venue identity matching supplied the entry."}}},"SportsFuturesResponse":{"type":"object","required":["futures"],"properties":{"futures":{"type":"array","items":{"$ref":"#/components/schemas/SportsFuturesEvent"}},"total":{"type":"integer","description":"Futures matching the filters across every page. Present only when `limit` is set."},"nextOffset":{"type":"integer","nullable":true,"description":"`offset` for the next page, or null on the last. Present only when `limit` is set."}}},"SportsFuturesEvent":{"type":"object","required":["eventId","title","image","sport","sportFamily","league","provider","volume","outcomes","marketsCount"],"properties":{"eventId":{"type":"string","example":"10088234"},"title":{"type":"string","example":"Super Bowl LX Winner"},"image":{"type":["string","null"]},"sport":{"type":"string","description":"Sports-catalog category id, or \"other\" if not classified into a known sport.","example":"football"},"sportFamily":{"type":"string","description":"Canonical category id from the sports catalog; equal to sport on this route.","example":"football"},"league":{"type":["string","null"],"example":"nfl"},"provider":{"type":"string","enum":["polymarket","predictfun"]},"volume":{"type":"number","format":"double","description":"Sum of contender market volumes. Always 0 for predictfun legs (no volume field upstream).","example":842311},"outcomes":{"type":"array","description":"Capped at 30 outcomes per event, highest-priced first with unpriced outcomes sunk to the bottom.","items":{"$ref":"#/components/schemas/SportsFuturesOutcome"}},"marketsCount":{"type":"integer","description":"Equal to len(outcomes) after capping.","example":32}}},"SportsFuturesOutcome":{"type":"object","required":["label","price","marketId","tokenId","conditionId"],"properties":{"label":{"type":"string","example":"Kansas City Chiefs"},"price":{"type":["number","null"],"format":"double","example":0.18},"marketId":{"type":"string","example":"10088235"},"tokenId":{"type":["string","null"]},"conditionId":{"type":["string","null"]}}},"SportsTournamentBracketResponse":{"type":"object","required":["leftRounds","rightRounds","final","winner"],"properties":{"leftRounds":{"type":"array","items":{"$ref":"#/components/schemas/SportsBracketRound"}},"rightRounds":{"type":"array","description":"Empty when format=left-to-right.","items":{"$ref":"#/components/schemas/SportsBracketRound"}},"final":{"oneOf":[{"$ref":"#/components/schemas/SportsBracketMatch"},{"type":"object","description":"Placeholder used when neither finalist is known yet.","properties":{"id":{"type":"string","example":"final-tbd"},"teams":{"type":"array","items":{"type":"null"}},"status":{"type":"string","example":"scheduled"},"subtitle":{"type":"string","example":"Final"}}}]},"winner":{"type":"null","description":"Always null — this endpoint never resolves a tournament champion."},"thirdPlace":{"oneOf":[{"$ref":"#/components/schemas/SportsBracketMatch"},{"type":"null"}],"description":"Present only for competitions that have a third-place fixture."},"groups":{"type":"array","description":"Present only when format=groups-then-knockout.","items":{"$ref":"#/components/schemas/SportsBracketGroup"}}}},"SportsBracketRound":{"type":"object","required":["name","shortName","matches"],"properties":{"name":{"type":"string","example":"Quarter-finals"},"shortName":{"type":"string","example":"QF"},"matches":{"type":"array","items":{"$ref":"#/components/schemas/SportsBracketMatch"}}}},"SportsBracketMatch":{"type":"object","required":["id","teams","status","subtitle","resolvesAfterExtraTime"],"properties":{"id":{"type":"string","example":"ucl-qf-1"},"teams":{"type":"array","minItems":2,"maxItems":2,"items":{"oneOf":[{"$ref":"#/components/schemas/SportsBracketTeam"},{"type":"null"}]}},"status":{"type":"string","enum":["scheduled","live","completed"]},"subtitle":{"type":"string","description":"Formatted fixture date, or \"Date1 / Date2\" for two-leg ties.","example":"Apr 9, 2026"},"resolvesAfterExtraTime":{"type":"boolean"},"kickoffTime":{"type":"string","format":"date-time"},"matchday":{"type":"integer","example":5},"scores":{"type":"array","minItems":2,"maxItems":2,"items":{"type":"integer"},"example":[2,1]},"winnerIdx":{"type":"integer","enum":[0,1]},"aggregateScores":{"type":"array","description":"Present for two-leg ties.","minItems":2,"maxItems":2,"items":{"type":"integer"},"example":[3,2]},"providerPrices":{"$ref":"#/components/schemas/SportsBracketProviderPrices"}}},"SportsBracketTeam":{"type":"object","required":["name","shortName","logo"],"properties":{"name":{"type":"string","example":"Real Madrid"},"shortName":{"type":"string","example":"RMA"},"logo":{"type":["string","null"]}}},"SportsBracketProviderPrices":{"type":"object","description":"Only providers with a resolvable market for this tie are present.","properties":{"polymarket":{"$ref":"#/components/schemas/SportsBracketProviderPricePoly"},"predictfun":{"$ref":"#/components/schemas/SportsBracketProviderPriceGeneric"},"kalshi":{"$ref":"#/components/schemas/SportsBracketProviderPriceKalshi"}}},"SportsBracketProviderPricePoly":{"type":"object","required":["slug","marketId","title","titles","prices","drawPrice","outcomeMarketIds"],"properties":{"slug":{"type":"string"},"marketId":{"type":["string","null"]},"title":{"type":"string"},"titles":{"type":"array","minItems":2,"maxItems":2,"items":{"type":"string"}},"prices":{"type":"array","minItems":2,"maxItems":2,"items":{"type":["number","null"],"format":"double"}},"drawPrice":{"type":["number","null"],"format":"double"},"outcomeMarketIds":{"type":"array","minItems":3,"maxItems":3,"items":{"type":["string","null"]}},"spreadLines":{"type":"array","items":{"$ref":"#/components/schemas/SportsBracketSpreadLine"}},"totalLines":{"type":"array","items":{"$ref":"#/components/schemas/SportsBracketTotalLine"}},"comboEligibility":{"type":"object","description":"Present only when the combo eligibility store has data for this tie.","additionalProperties":{"$ref":"#/components/schemas/SportsComboEligibilityEntry"}}}},"SportsBracketSpreadLine":{"type":"object","properties":{"line":{"type":["number","null"],"format":"double"},"team0Price":{"type":["number","null"],"format":"double"},"team1Price":{"type":["number","null"],"format":"double"},"team0MarketId":{"type":["string","null"]},"team1MarketId":{"type":["string","null"]}}},"SportsBracketTotalLine":{"type":"object","properties":{"line":{"type":["number","null"],"format":"double"},"overPrice":{"type":["number","null"],"format":"double"},"underPrice":{"type":["number","null"],"format":"double"},"overMarketId":{"type":["string","null"]},"underMarketId":{"type":["string","null"]}}},"SportsBracketProviderPriceGeneric":{"type":"object","required":["slug","marketId","title","titles","prices","drawPrice","outcomeMarketIds"],"properties":{"slug":{"type":"string"},"marketId":{"type":["string","null"]},"title":{"type":"string"},"titles":{"type":"array","minItems":2,"maxItems":2,"items":{"type":"string"}},"prices":{"type":"array","minItems":2,"maxItems":2,"items":{"type":["number","null"],"format":"double"}},"drawPrice":{"type":["number","null"],"format":"double"},"outcomeMarketIds":{"type":"array","minItems":3,"maxItems":3,"items":{"type":["string","null"]}}}},"SportsBracketProviderPriceKalshi":{"type":"object","required":["eventTicker","marketId","title","titles","prices","drawPrice","outcomeMarketIds"],"properties":{"eventTicker":{"type":"string"},"marketId":{"type":["string","null"]},"title":{"type":"string"},"titles":{"type":"array","minItems":2,"maxItems":2,"items":{"type":"string"}},"prices":{"type":"array","minItems":2,"maxItems":2,"items":{"type":["number","null"],"format":"double"}},"drawPrice":{"type":["number","null"],"format":"double"},"outcomeMarketIds":{"type":"array","minItems":3,"maxItems":3,"items":{"type":["string","null"]}}}},"SportsComboEligibilityEntry":{"type":"object","required":["yesPositionId","noPositionId"],"properties":{"yesPositionId":{"type":"string"},"noPositionId":{"type":"string"}}},"SportsBracketGroup":{"type":"object","required":["name","shortName","table","matches"],"properties":{"name":{"type":"string","example":"Group A"},"shortName":{"type":"string","example":"A"},"table":{"type":"array","items":{"$ref":"#/components/schemas/SportsBracketStanding"}},"matches":{"type":"array","items":{"$ref":"#/components/schemas/SportsBracketMatch"}}}},"SportsBracketStanding":{"type":"object","required":["team","played","won","draw","lost","goalsFor","goalsAgainst","points","goalDifference"],"properties":{"team":{"$ref":"#/components/schemas/SportsBracketTeam"},"played":{"type":"integer"},"won":{"type":"integer"},"draw":{"type":"integer"},"lost":{"type":"integer"},"goalsFor":{"type":"integer"},"goalsAgainst":{"type":"integer"},"points":{"type":"integer"},"goalDifference":{"type":"integer"}}},"SportsGameMarketsResponse":{"type":"object","required":["slug","teamA","teamB","teamALogo","teamBLogo","sport","sections"],"properties":{"slug":{"type":"string","example":"nba-lal-bos-2026-01-15"},"teamA":{"type":["string","null"],"example":"Los Angeles Lakers"},"teamB":{"type":["string","null"],"example":"Boston Celtics"},"teamALogo":{"type":["string","null"]},"teamBLogo":{"type":["string","null"]},"sport":{"type":["string","null"],"example":"nba"},"sections":{"$ref":"#/components/schemas/SportsGameMarketsSections"}}},"SportsGameMarketsSections":{"type":"object","required":["gameLines","halves","playerProps","moreMarkets","exactScore","corners"],"properties":{"gameLines":{"type":"array","items":{"$ref":"#/components/schemas/SportsGameMarketFamily"}},"halves":{"type":"array","items":{"$ref":"#/components/schemas/SportsGameMarketFamily"}},"playerProps":{"type":"array","items":{"$ref":"#/components/schemas/SportsGameMarketFamily"}},"moreMarkets":{"type":"array","items":{"$ref":"#/components/schemas/SportsGameMarketFamily"}},"exactScore":{"type":"array","items":{"$ref":"#/components/schemas/SportsGameMarketFamily"}},"corners":{"type":"array","items":{"$ref":"#/components/schemas/SportsGameMarketFamily"}}}},"SportsGameMarketFamily":{"type":"object","description":"A market family from one venue. layout=pills carries a flat legs list; layout=ladder carries spread/total rungs grouped by line value. Polymarket families may be combo-eligible; Predict.fun families never are.","required":["key","title","section","layout","provider","mergeKey","volume"],"properties":{"key":{"type":"string","example":"moneyline"},"title":{"type":"string","example":"Moneyline"},"section":{"type":"string","enum":["gameLines","halves","playerProps","moreMarkets","exactScore","corners"]},"layout":{"type":"string","enum":["pills","ladder"]},"provider":{"type":"string","enum":["polymarket","predictfun","kalshi","hyperliquid"],"description":"Venue for every market in this family."},"mergeKey":{"type":"string","description":"Opaque server-assigned identity for equivalent families across venues. Group pill families by this value rather than inferring identity from titles.","example":"moneyline"},"volume":{"type":"number","format":"double"},"kind":{"type":"string","enum":["spread","total"],"description":"Only present when layout=ladder."},"legs":{"type":"array","description":"Only present when layout=pills.","items":{"$ref":"#/components/schemas/SportsGameMarketLeg"}},"rungs":{"type":"array","description":"Only present when layout=ladder.","items":{"$ref":"#/components/schemas/SportsGameMarketRung"}}}},"SportsGameMarketRung":{"type":"object","required":["line","legs"],"properties":{"line":{"type":["number","null"],"format":"double","example":220.5},"legs":{"type":"array","items":{"$ref":"#/components/schemas/SportsGameMarketLeg"}}}},"SportsGameMarketLeg":{"type":"object","required":["label","marketId","price","side","outcomeKey"],"properties":{"label":{"type":"string","example":"Celtics -4.5"},"marketId":{"type":"string","example":"587234"},"price":{"type":"number","format":"double","minimum":0,"exclusiveMaximum":1,"description":"Probability-scale price. Predict.fun may return 0 when an open market has no usable quote; 0 is unavailable pricing, not an executable price.","example":0.52},"side":{"type":"string","enum":["yes","no"],"description":"Contract side represented by this leg. Select the matching position ID for combos; a no-side leg must use noPositionId."},"outcomeKey":{"type":"string","description":"Opaque server-assigned identity for equivalent outcome rows across venues.","example":"team:boston celtics"},"comboEligible":{"type":"boolean","description":"Present only when eligibility is known. Predict.fun legs always set false; an absent field means unknown, not false."},"yesPositionId":{"type":["string","null"],"description":"Present when eligibility is known. Use only for side=yes."},"noPositionId":{"type":["string","null"],"description":"Present when eligibility is known. Use only for side=no."}}},"SportsComboMarketsResponse":{"type":"object","required":["markets"],"properties":{"markets":{"type":"array","items":{"$ref":"#/components/schemas/SportsComboMarket"}}}},"SportsComboMarket":{"type":"object","required":["id","conditionId","yesPositionId","noPositionId","slug","title","tags"],"properties":{"id":{"type":"string","description":"Polymarket market id.","example":"587234"},"conditionId":{"type":"string","example":"0xabc123..."},"yesPositionId":{"type":"string","example":"10897234..."},"noPositionId":{"type":"string","example":"10897235..."},"slug":{"type":"string","example":"nba-lal-bos-2026-01-15-bos"},"title":{"type":"string","example":"Celtics -4.5"},"tags":{"type":"array","items":{"type":"string"},"example":["nba","spread"]}}},"SportsMetadataResponse":{"type":"object","required":["teams","leagues"],"properties":{"teams":{"type":"array","items":{"$ref":"#/components/schemas/SportsTeam"}},"leagues":{"type":"array","items":{"$ref":"#/components/schemas/SportsLeague"}}}},"SportsTeam":{"type":"object","required":["id","name","league","logoUrl","abbreviation","alias","color"],"properties":{"id":{"type":"string","description":"Internal row id (numeric, serialized as string).","example":"142"},"name":{"type":"string","example":"Boston Celtics"},"league":{"type":"string","example":"NBA"},"logoUrl":{"type":["string","null"]},"abbreviation":{"type":["string","null"],"example":"BOS"},"alias":{"type":["string","null"],"example":"Celtics"},"color":{"type":["string","null"],"example":"#007A33"}}},"SportsLeague":{"type":"object","required":["id","sport","imageUrl"],"properties":{"id":{"type":"string","example":"8"},"sport":{"type":"string","example":"basketball"},"imageUrl":{"type":["string","null"]}}},"SportsMatchedMarketsResponse":{"type":"object","required":["pairs","count","limit","offset","has_more"],"properties":{"pairs":{"type":"array","items":{"$ref":"#/components/schemas/SportsMatchedMarketPair"}},"count":{"type":"integer","description":"Number of pairs on this page after live-status post-filtering (may be less than limit).","example":42},"limit":{"type":"integer","example":50},"offset":{"type":"integer","example":0},"has_more":{"type":"boolean","description":"Whether the underlying page (before live-status post-filtering) was full — not a strict function of count."},"next_cursor":{"type":["string","null"],"description":"Opaque cursor for the next keyset page. Present when cursor mode is requested."},"catalog_version":{"type":"string","description":"Full-set fingerprint shared by every page in one cursor walk."},"total":{"type":"integer","description":"Only present when include_total=true. Total matching-pair count under the pre-live-filter query filters.","example":1284}}},"SportsMatchedMarketPair":{"type":"object","required":["a","b","similarity","updated_at"],"properties":{"a":{"$ref":"#/components/schemas/SportsMatchedMarketSide"},"b":{"$ref":"#/components/schemas/SportsMatchedMarketSide"},"similarity":{"type":"number","format":"double","minimum":0.82,"maximum":1,"example":0.94},"updated_at":{"type":"string","format":"date-time"}}},"SportsMatchedMarketSide":{"type":"object","required":["provider_id","provider","market_id","title","ticker","image","icon","expires_at","category"],"properties":{"provider_id":{"type":"integer","example":2},"provider":{"type":"string","example":"polymarket"},"market_id":{"type":"string","example":"587234"},"title":{"type":["string","null"],"description":"Null if metadata for this side's market isn't available.","example":"Will the Celtics win?"},"ticker":{"type":["string","null"]},"image":{"type":["string","null"]},"icon":{"type":["string","null"]},"expires_at":{"type":["string","null"],"format":"date-time"},"category":{"type":["string","null"],"description":"Canonical title-cased category, or null when the market is uncategorised."}}},"SportsEnrichedMatchedMarketsResponse":{"allOf":[{"$ref":"#/components/schemas/SportsMatchedMarketsResponse"},{"type":"object","required":["pairs"],"properties":{"pairs":{"type":"array","items":{"$ref":"#/components/schemas/SportsEnrichedMatchedMarketPair"}}}}]},"SportsEnrichedMatchedMarketPair":{"allOf":[{"$ref":"#/components/schemas/SportsMatchedMarketPair"},{"type":"object","required":["a","b"],"properties":{"a":{"$ref":"#/components/schemas/SportsEnrichedMatchedMarketSide"},"b":{"$ref":"#/components/schemas/SportsEnrichedMatchedMarketSide"}}}]},"SportsEnrichedMatchedMarketSide":{"allOf":[{"$ref":"#/components/schemas/SportsMatchedMarketSide"},{"type":"object","required":["details","pricing"],"properties":{"details":{"description":"Indexed market identifiers and outcomes, or null when no market row resolves.","oneOf":[{"$ref":"#/components/schemas/MarketsDetail"},{"type":"null"}]},"pricing":{"description":"Current venue reference price, or null when unsupported or unavailable.","oneOf":[{"$ref":"#/components/schemas/MarketsPrice"},{"type":"null"}]}}}]},"PerpetualVenue":{"type":"string","description":"Canonical perpetual venue identifier.","enum":["hyperliquid","polymarket_perps","kalshi_margin"]},"PerpetualVenueCapabilities":{"type":"object","additionalProperties":false,"required":["venue","environment","market_data_service","instruments","book","trades","candles","funding","market_state","notes"],"properties":{"venue":{"$ref":"#/components/schemas/PerpetualVenue"},"environment":{"type":"string","description":"Venue-native environment name; do not infer it from Kairos staging."},"market_data_service":{"type":"string","const":"agora","description":"Legacy compatibility identifier; live REST snapshots are owned by the Market Data API, not this metadata endpoint."},"instruments":{"type":"boolean"},"book":{"type":"boolean"},"trades":{"type":"boolean"},"candles":{"type":"boolean"},"funding":{"type":"boolean"},"market_state":{"type":"boolean"},"notes":{"type":"array","items":{"type":"string"}}}},"HyperliquidPublicMetadata":{"type":"object","additionalProperties":false,"required":["kind","listing_namespace","margin_table_id","venue_margin_mode"],"properties":{"kind":{"type":"string","const":"hyperliquid"},"listing_namespace":{"type":"string","const":"validator_main_dex"},"margin_table_id":{"type":"integer"},"venue_margin_mode":{"type":["string","null"]}}},"PolymarketRiskTier":{"type":"object","additionalProperties":false,"required":["lower_bound","max_leverage"],"properties":{"lower_bound":{"type":"string","description":"Exact decimal-string lower bound for the tier."},"max_leverage":{"type":"integer"}}},"PolymarketPublicMetadata":{"type":"object","additionalProperties":false,"required":["kind","category","funding_interval","price_decimals","risk_tiers"],"properties":{"kind":{"type":"string","const":"polymarket_perps"},"category":{"type":"string"},"funding_interval":{"type":"string"},"price_decimals":{"type":"integer"},"risk_tiers":{"type":"array","items":{"$ref":"#/components/schemas/PolymarketRiskTier"}}}},"KalshiMarketSchedule":{"type":"object","additionalProperties":false,"required":["is_open","next_close_ts","next_open_ts"],"properties":{"is_open":{"type":"boolean"},"next_close_ts":{"type":["integer","null"],"description":"Venue Unix timestamp, or null when no next close is published."},"next_open_ts":{"type":["integer","null"],"description":"Venue Unix timestamp, or null when no next open is published."}}},"KalshiSampledLeverage":{"type":"object","additionalProperties":false,"required":["semantics","leverage","sample_notional_usd"],"properties":{"semantics":{"type":"string","const":"sampled_estimate"},"leverage":{"type":"string","description":"Exact decimal-string leverage estimate."},"sample_notional_usd":{"type":["string","null"],"description":"Exact decimal-string sample notional, or null."}}},"KalshiPublicMetadata":{"type":"object","additionalProperties":false,"required":["kind","title","fractional_trading_enabled","sampled_leverage","sampled_leverage_curve","schedule"],"properties":{"kind":{"type":"string","const":"kalshi_margin"},"title":{"type":"string"},"fractional_trading_enabled":{"type":"boolean"},"sampled_leverage":{"oneOf":[{"$ref":"#/components/schemas/KalshiSampledLeverage"},{"type":"null"}]},"sampled_leverage_curve":{"type":"array","items":{"$ref":"#/components/schemas/KalshiSampledLeverage"}},"schedule":{"oneOf":[{"$ref":"#/components/schemas/KalshiMarketSchedule"},{"type":"null"}]}}},"PublicInstrumentMetadata":{"oneOf":[{"$ref":"#/components/schemas/HyperliquidPublicMetadata"},{"$ref":"#/components/schemas/PolymarketPublicMetadata"},{"$ref":"#/components/schemas/KalshiPublicMetadata"}],"discriminator":{"propertyName":"kind","mapping":{"hyperliquid":"#/components/schemas/HyperliquidPublicMetadata","polymarket_perps":"#/components/schemas/PolymarketPublicMetadata","kalshi_margin":"#/components/schemas/KalshiPublicMetadata"}}},"PerpetualInstrument":{"type":"object","additionalProperties":false,"required":["instrument_id","venue","integration_id","environment","venue_instrument_id","display_symbol","base_asset_id","quote_asset_id","collateral_asset_id","settlement_asset_id","native_quantity_unit","contract_multiplier","status","isolated_only","max_leverage","price_increment","size_increment","metadata"],"properties":{"instrument_id":{"type":"string","description":"Stable Kairos instrument identity. Hyperliquid standard assets use\n`hl-mainnet-{base}-usdt`; HYPE and PURR use the USDC exception.\n","examples":["hl-mainnet-btc-usdt","hl-mainnet-hype-usdc","hl-mainnet-purr-usdc"]},"venue":{"$ref":"#/components/schemas/PerpetualVenue"},"integration_id":{"type":"string","description":"Product-specific routing and credential boundary."},"environment":{"type":"string"},"venue_instrument_id":{"type":"string","description":"Exact identifier accepted by the venue and Market Data API snapshot endpoint."},"display_symbol":{"type":"string","examples":["BTC-USDT","HYPE-USDC","PURR-USDC"]},"base_asset_id":{"type":"string"},"quote_asset_id":{"type":"string","description":"Hyperliquid uses USDT except for HYPE and PURR, which use USDC.","examples":["USDT","USDC"]},"collateral_asset_id":{"type":"string"},"settlement_asset_id":{"type":["string","null"]},"native_quantity_unit":{"type":"string","enum":["base_asset","contracts"]},"contract_multiplier":{"type":["string","null"],"description":"Exact decimal string, or null when no authoritative conversion exists."},"status":{"type":"string","enum":["active","inactive","closed","delisted","unknown"]},"isolated_only":{"type":["boolean","null"],"description":"Null when the venue does not publish an authoritative mode."},"max_leverage":{"type":["string","null"],"description":"Exact decimal string when published by the venue."},"price_increment":{"type":["string","null"],"description":"Exact venue price increment; null when not established."},"size_increment":{"type":["string","null"],"description":"Exact venue quantity increment; null when not established."},"metadata":{"$ref":"#/components/schemas/PublicInstrumentMetadata"}}},"DiscoverTrendingWsMarket":{"type":"object","description":"Trending market reduced to what a live-price subscriber needs.","properties":{"id":{"type":"string"},"symbol":{"type":["string","null"]},"title":{"type":"string"},"price":{"type":"number"},"volume24h":{"type":"number"},"provider":{"type":"string"}}},"DiscoverTrendingWsResponse":{"type":"object","required":["markets","count","timestamp"],"properties":{"markets":{"type":"array","items":{"$ref":"#/components/schemas/DiscoverTrendingWsMarket"}},"count":{"type":"integer"},"timestamp":{"type":"string"}}},"MarketClusterMember":{"type":"object","description":"One market inside a cluster, after the venue allowlist and stale-status filter.","required":["provider_id","provider","market_id"],"properties":{"provider_id":{"type":"integer","description":"Numeric provider id as stored."},"provider":{"type":"string","description":"Provider name resolved from provider_id.","example":"polymarket"},"market_id":{"type":"string"},"title":{"type":["string","null"]},"ticker":{"type":["string","null"]},"image":{"type":["string","null"]},"icon":{"type":["string","null"]},"expires_at":{"type":["string","null"],"description":"ISO-8601 expiry, null when the venue publishes none."}}},"MarketCluster":{"type":"object","required":["cluster_id","floor","truncated","size","members"],"properties":{"cluster_id":{"type":"string"},"floor":{"type":"string","description":"Tier floor this membership was resolved at.","enum":["exact","semantic"]},"truncated":{"type":"boolean","description":"True when the cluster holds more members than the 32 returned."},"size":{"type":"integer","description":"Cluster size as stored — counts members this response filtered out."},"members":{"type":"array","maxItems":32,"items":{"$ref":"#/components/schemas/MarketClusterMember"}}}},"MarketClustersResponse":{"type":"object","required":["clusters","count","floor"],"properties":{"clusters":{"type":"object","description":"Keyed by the requested `<provider_id>:<market_id>` reference. A reference with no cluster, or whose cluster has fewer than two live members after filtering, is absent — the map is never padded with empties.\n","additionalProperties":{"$ref":"#/components/schemas/MarketCluster"}},"count":{"type":"integer","description":"Number of entries in `clusters`."},"floor":{"type":"string","enum":["exact","semantic"]}}},"TxoddsTeamRef":{"type":"object","required":["code","name"],"properties":{"code":{"type":"string"},"name":{"type":"string"}}},"TxoddsWinProb":{"type":"object","required":["home","draw","away"],"properties":{"home":{"type":"number"},"draw":{"type":"number"},"away":{"type":"number"}}},"TxoddsFixtureListItem":{"type":"object","required":["fixtureId","competitionId","competition","home","away","kickoff"],"properties":{"fixtureId":{"type":"integer"},"competitionId":{"type":"integer"},"competition":{"type":"string"},"home":{"$ref":"#/components/schemas/TxoddsTeamRef"},"away":{"$ref":"#/components/schemas/TxoddsTeamRef"},"kickoff":{"type":"integer","description":"Kick-off, epoch milliseconds."},"winProb":{"oneOf":[{"$ref":"#/components/schemas/TxoddsWinProb"},{"type":"null"}]},"status":{"type":"string","default":"scheduled","description":"Derived from feed phase plus wall clock at cache time, so it can be up to one cache TTL stale."},"homeScore":{"type":"integer","default":0},"awayScore":{"type":"integer","default":0},"sport":{"type":"string","default":"soccer"}}},"TxoddsFixtureList":{"type":"object","required":["items","limit","offset"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/TxoddsFixtureListItem"}},"limit":{"type":"integer","description":"Echo of the requested page size."},"offset":{"type":"integer"}}},"TxoddsScoreStats":{"type":"object","description":"Latest score state. Soccer-shaped; gridiron fixtures carry their counters in `football`.","required":["gameState","homeGoals","awayGoals","homeYellow","awayYellow","homeRed","awayRed","homeCorners","awayCorners","possessionHome","possessionAway","shotsHome","shotsAway","shotsOnTargetHome","shotsOnTargetAway","updateCount"],"properties":{"gameState":{"type":"string"},"homeGoals":{"type":"integer"},"awayGoals":{"type":"integer"},"homeYellow":{"type":"integer"},"awayYellow":{"type":"integer"},"homeRed":{"type":"integer"},"awayRed":{"type":"integer"},"homeCorners":{"type":"integer"},"awayCorners":{"type":"integer"},"possessionHome":{"type":["number","null"]},"possessionAway":{"type":["number","null"]},"shotsHome":{"type":["integer","null"]},"shotsAway":{"type":["integer","null"]},"shotsOnTargetHome":{"type":["integer","null"]},"shotsOnTargetAway":{"type":["integer","null"]},"updateCount":{"type":"integer"},"currentHolder":{"type":["string","null"]},"possessionThreat":{"type":["string","null"]}}},"TxoddsTimelineEvent":{"type":"object","required":["ts","seq","minute","action","gameState","homeGoals","awayGoals","side","data"],"properties":{"ts":{"type":"integer","description":"Event time, epoch milliseconds."},"seq":{"type":"integer"},"minute":{"type":["integer","null"]},"action":{"type":"string"},"gameState":{"type":"string"},"homeGoals":{"type":"integer"},"awayGoals":{"type":"integer"},"side":{"type":["string","null"]},"data":{"type":"string"},"detail":{"type":["string","null"]}}},"TxoddsMomentumPoint":{"type":"object","required":["minute","home","away"],"properties":{"minute":{"type":"integer"},"home":{"type":"integer"},"away":{"type":"integer"}}},"TxoddsConversion":{"type":"object","required":["homeShots","awayShots","homeConversion","awayConversion"],"properties":{"homeShots":{"type":["integer","null"]},"awayShots":{"type":["integer","null"]},"homeConversion":{"type":["number","null"]},"awayConversion":{"type":["number","null"]}}},"TxoddsTurnover":{"type":"object","required":["switches","perMinute"],"properties":{"switches":{"type":"integer"},"perMinute":{"type":["number","null"]}}},"TxoddsMetrics":{"type":"object","description":"Possession-derived metrics. Empty shapes for gridiron fixtures, which emit no possession events, and for any part that failed while the rest of the detail loaded.","required":["avgPossessionHome","avgPossessionAway","momentum","conversion","turnover"],"properties":{"avgPossessionHome":{"type":["number","null"]},"avgPossessionAway":{"type":["number","null"]},"momentum":{"type":"array","items":{"$ref":"#/components/schemas/TxoddsMomentumPoint"}},"conversion":{"$ref":"#/components/schemas/TxoddsConversion"},"turnover":{"$ref":"#/components/schemas/TxoddsTurnover"}}},"TxoddsFootballDown":{"type":"object","required":["number","yardsToGo","possession"],"properties":{"number":{"type":"integer"},"yardsToGo":{"type":"integer"},"yardsToEndzone":{"type":["integer","null"]},"possession":{"type":"string"}}},"TxoddsFootballPeriodScore":{"type":"object","required":["period","home","away"],"properties":{"period":{"type":"string"},"home":{"type":"integer"},"away":{"type":"integer"}}},"TxoddsFootballState":{"type":"object","description":"US-football situational state. Present only when `sport` is `usfootball`.","required":["homePoints","awayPoints","touchdowns","fieldGoals","periodScores"],"properties":{"homePoints":{"type":"integer"},"awayPoints":{"type":"integer"},"touchdowns":{"type":"array","items":{"type":"integer"}},"fieldGoals":{"type":"array","items":{"type":"integer"}},"onePointConversions":{"type":"array","items":{"type":"integer"}},"twoPointConversions":{"type":"array","items":{"type":"integer"}},"safeties":{"type":"array","items":{"type":"integer"}},"periodScores":{"type":"array","items":{"$ref":"#/components/schemas/TxoddsFootballPeriodScore"}},"down":{"oneOf":[{"$ref":"#/components/schemas/TxoddsFootballDown"},{"type":"null"}]},"clockSeconds":{"type":["integer","null"]},"clockRunning":{"type":"boolean","default":false},"redZone":{"type":"boolean","default":false},"phase":{"type":"string","default":""}}},"TxoddsFixtureTiming":{"properties":{"fixtureId":{"title":"Fixtureid","type":"integer"},"competitionId":{"title":"Competitionid","type":"integer"},"sport":{"title":"Sport","type":"string"},"source":{"default":"txodds","title":"Source","type":"string"},"phase":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Phase"},"status":{"default":"unknown","title":"Status","type":"string"},"observedAt":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Observedat"},"evaluatedAt":{"title":"Evaluatedat","type":"string"},"stateAgeSeconds":{"anyOf":[{"type":"number"},{"type":"null"}],"default":null,"title":"Stateageseconds"},"staleAfterSeconds":{"default":15,"title":"Staleafterseconds","type":"integer"},"stale":{"default":true,"title":"Stale","type":"boolean"},"clockSeconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Clockseconds"},"clockRunning":{"anyOf":[{"type":"boolean"},{"type":"null"}],"default":null,"title":"Clockrunning"},"clockDirection":{"default":"unknown","title":"Clockdirection","type":"string"},"periodRemainingSeconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Periodremainingseconds"},"regulationRemainingSeconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Regulationremainingseconds"},"gameRemainingSeconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Gameremainingseconds"},"elapsedMatchSeconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"title":"Elapsedmatchseconds"},"lateGameBasis":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Lategamebasis"},"usableForLateGame":{"default":false,"title":"Usableforlategame","type":"boolean"},"unavailableReason":{"anyOf":[{"type":"string"},{"type":"null"}],"default":"missing_score_observation","title":"Unavailablereason"}},"required":["fixtureId","competitionId","sport","evaluatedAt"],"title":"FixtureTiming","type":"object","description":"Source-observed fixture timing. Eligibility describes supported clock evidence, not whether a game is late or a market is safe to trade. Soccer exposes the nominal second-half clock, excluding any unknown added time; NFL exposes regulation clock time. No field predicts the final whistle or extrapolates between observations."},"TxoddsFixtureDetail":{"type":"object","description":"Everything the match detail view renders, from the score event store.","required":["fixtureId","competitionId","competition","home","away","kickoff","status","score","timeline","metrics"],"properties":{"fixtureId":{"type":"integer"},"competitionId":{"type":"integer"},"competition":{"type":"string"},"home":{"$ref":"#/components/schemas/TxoddsTeamRef"},"away":{"$ref":"#/components/schemas/TxoddsTeamRef"},"kickoff":{"type":"integer"},"status":{"type":"string"},"sport":{"type":"string","default":"soccer"},"football":{"oneOf":[{"$ref":"#/components/schemas/TxoddsFootballState"},{"type":"null"}]},"score":{"$ref":"#/components/schemas/TxoddsScoreStats"},"timeline":{"type":"array","items":{"$ref":"#/components/schemas/TxoddsTimelineEvent"}},"metrics":{"$ref":"#/components/schemas/TxoddsMetrics"}}},"TxoddsWinProbSample":{"type":"object","required":["ts","minute","home","draw","away"],"properties":{"ts":{"type":"integer"},"minute":{"type":"integer"},"home":{"type":"number"},"draw":{"type":"number"},"away":{"type":"number"}}},"TxoddsMarketRead":{"type":"object","required":["expectedGoals","supremacyHome","projHome","projAway"],"properties":{"expectedGoals":{"type":["number","null"]},"supremacyHome":{"type":["number","null"]},"projHome":{"type":["number","null"]},"projAway":{"type":["number","null"]}}},"TxoddsFixtureTimeseries":{"type":"object","description":"Odds-derived curves, split out from the detail because they read a table orders of magnitude larger than the score store.","required":["fixtureId","winProbHistory","marketRead"],"properties":{"fixtureId":{"type":"integer"},"winProbHistory":{"type":"array","items":{"$ref":"#/components/schemas/TxoddsWinProbSample"}},"marketRead":{"$ref":"#/components/schemas/TxoddsMarketRead"}}},"ArbBetsStrategy":{"type":"object","description":"The two-leg bet the vendor scored as best for this opportunity.","properties":{"combination":{"type":"string"},"platform_1":{"type":"string"},"side_1":{"type":"string","enum":["Yes","No"]},"price_1":{"type":"number"},"bet_amount_1":{"type":"number"},"payout_1":{"type":"number"},"platform_2":{"type":"string"},"side_2":{"type":"string","enum":["Yes","No"]},"price_2":{"type":"number"},"bet_amount_2":{"type":"number"},"payout_2":{"type":"number"},"total_prob":{"type":"number","description":"Combined implied probability. Below 1 is what makes the pair an arb."},"gross_profit":{"type":"number"},"net_profit":{"type":"number"},"roi_percent":{"type":"number"},"fees":{"type":"number"}}},"ArbBetsOpportunity":{"type":"object","properties":{"kalshi_id":{"type":"string"},"poly_clob_token_ids":{"type":"array","items":{"type":"string"}},"market_name_a":{"type":"string"},"market_name_b":{"type":"string"},"platform_a":{"type":"string"},"platform_b":{"type":"string"},"url_a":{"type":"string"},"url_b":{"type":"string"},"volume_a":{"type":"number"},"volume_b":{"type":"number"},"best_arbitrage":{"$ref":"#/components/schemas/ArbBetsStrategy"}}},"ArbBetsResponse":{"type":"object","description":"Vendor payload, passed through unchanged. Fields are documented as observed — the vendor owns this shape and can change it without a Kairos deploy.\n","properties":{"success":{"type":"boolean"},"generated_at":{"type":"string"},"investment_amount":{"type":"number"},"min_profit_filter":{"type":"number"},"total_markets_analyzed":{"type":"integer"},"total_arbitrages_found":{"type":"integer"},"markets_skipped":{"type":"integer"},"skip_reasons":{"type":"object","additionalProperties":{"type":"integer"}},"profitable_count":{"type":"integer"},"profitable_arbitrages":{"type":"array","items":{"$ref":"#/components/schemas/ArbBetsOpportunity"}}}},"MarketLinkVenue":{"type":"object","description":"One venue's listing of a linked contract, in that venue's own keys.","required":["provider","marketId","streamKey","tokenIdYes","tokenIdNo","executable"],"properties":{"provider":{"type":"string","enum":["polymarket","predictfun","kalshi","hyperliquid"]},"marketId":{"type":"string","description":"Executor-convention market id (Polymarket condition id, Predict.fun numeric id, Kalshi ticker, Hyperliquid outcome id)."},"streamKey":{"type":"string","description":"Stream-side id (Polymarket's numeric Gamma id; the market id elsewhere)."},"marketIdNo":{"type":"string","description":"The venue market that books the link's NO side where it is not `marketId` (Kalshi lists a game as one ticker per team). Present only on such legs."},"streamKeyNo":{"type":"string"},"tokenIdYes":{"type":"string","nullable":true,"description":"The token the union book reads for the link's YES side on this venue."},"tokenIdNo":{"type":"string","nullable":true},"executable":{"type":"boolean","description":"Whether `GET /orders/route-fees` reports this venue routable for the link; false for a display-only leg."}}},"MarketLinkRow":{"type":"object","description":"One cross-venue link as the catalog publishes it.","required":["id","title","image","league","sport","startTime","venues","syntheticIdYes","syntheticIdNo","executableVenues","rank","sideLabels","primary"],"properties":{"id":{"type":"string","format":"uuid","description":"The `MarketLink` id."},"title":{"type":"string","description":"The event title from Discover's row for the primary venue, else the link's own title."},"image":{"type":"string","nullable":true},"league":{"type":"string","nullable":true,"description":"League named by Discover's sports chip (e.g. `NFL`); null where the chip is the sport itself or the link is not sports."},"sport":{"type":"string","nullable":true,"description":"Sport family (e.g. `Football`, `Soccer`)."},"startTime":{"type":"string","format":"date-time","nullable":true,"description":"Earliest leg expiration; venues close a game market at its scheduled start."},"slug":{"type":"string","description":"The venue's game slug when Discover carries one; absent otherwise."},"venues":{"type":"array","minItems":2,"maxItems":4,"items":{"$ref":"#/components/schemas/MarketLinkVenue"}},"syntheticIdYes":{"type":"string","description":"Id of the union book for the link's YES side (`synthetic:<id>` on the stream)."},"syntheticIdNo":{"type":"string"},"executableVenues":{"type":"array","items":{"type":"string"},"description":"Providers whose legs are `executable`, in leg order."},"rank":{"type":"number","description":"The largest Discover hourly-volume score across legs; the featured ordering key, never a displayed volume."},"sideLabels":{"type":"object","required":["yes","no"],"properties":{"yes":{"type":"string"},"no":{"type":"string"}}},"primary":{"type":"object","required":["provider","marketId"],"properties":{"provider":{"type":"string"},"marketId":{"type":"string"}},"description":"The leg whose Discover row supplied the title, else the first executable leg."}}},"MarketLinksFeaturedResponse":{"type":"object","required":["links","nextCursor"],"properties":{"links":{"type":"array","items":{"$ref":"#/components/schemas/MarketLinkRow"}},"nextCursor":{"type":"string","nullable":true,"description":"Pass as `cursor` for the next page; null on the last page."}}},"MarketLinksLookupResponse":{"type":"object","required":["links"],"properties":{"links":{"type":"object","description":"Keyed by `<provider>:<marketId>` as requested; markets without a link are omitted.","additionalProperties":{"$ref":"#/components/schemas/MarketLinkRow"}}}}},"responses":{"DataUnauthorized":{"description":"No valid credential presented — missing/invalid API-key headers, or an invalid/expired session token.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"},"example":{"detail":"Not authenticated"}}}},"DataValidationError":{"description":"Request failed FastAPI parameter validation.","content":{"application/json":{"schema":{"type":"object","required":["detail"],"properties":{"detail":{"type":"array","description":"One entry per failed field, with location, message, and error type.","items":{"type":"object","additionalProperties":true}}}}}}},"DataForbidden":{"description":"Authenticated but not permitted. Three distinct causes: the session user is not invited\n(`Invite required`), the API key lacks the operation's scope, or API-key access to the\nrequested venue is switched off (`API access is disabled for <provider>`). Admin and API-key\ncallers bypass the invite check; session and admin callers bypass scope and venue checks.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"},"example":{"detail":"Invite required"}}}},"DataRateLimited":{"description":"Rate limit exceeded for this route's sliding window, keyed on the session `sub` when\nauthenticated and on the trusted client IP otherwise. Honor `Retry-After`. A per-API-key\ndata ceiling (set per credential) rejects with `{\"detail\": \"API key data rate limit\nexceeded\"}` instead of the `error` envelope below.\n","headers":{"Retry-After":{"schema":{"type":"integer"}},"X-RateLimit-Limit":{"schema":{"type":"integer"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}},"X-RateLimit-Reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","example":"Rate limit exceeded: 100 per 1 minute"}}}}}},"DataPayloadTooLarge":{"description":"Request body exceeded the 8 MiB service-wide cap, refused before the handler ran.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"},"example":{"detail":"Request body too large"}}}}}},"paths":{"/perpetuals/venues":{"get":{"operationId":"listPerpetualVenues","summary":"List perpetual venue capabilities","description":"Returns the supported perpetual venue identifiers and their public-data\ncapabilities. This is metadata owned by the Python Data API. It does not\nserve books, trades, candles, funding observations, or WebSocket data.\n\nThis preview exists only in Kairos staging. When the preview flag is\ndisabled, the route fails closed with 404. Live REST snapshots are served\nseparately by the Market Data API; anonymous WebSocket transport is served by\n`websocket_cpp`.\n","tags":["Perpetuals"],"x-kairos-auth":"public","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","security":[],"servers":[{"url":"https://staging-data.kairos.trade","description":"Staging preview only"}],"parameters":[],"responses":{"200":{"description":"Canonical venue capability records.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PerpetualVenueCapabilities"}}}}},"404":{"description":"The staging preview is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"},"example":{"detail":"Perpetual metadata is disabled"}}}},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/perpetuals/instruments":{"get":{"operationId":"listPerpetualInstruments","summary":"Discover perpetual instruments","description":"Returns strict canonical instrument metadata, optionally for one venue.\nFinancial values are exact decimal strings or null; consumers must not\ninvent a contract multiplier, tick, or leverage value when one is absent.\nResults are cached independently per venue for 60 seconds.\n\nThis endpoint owns discovery and instrument rules only. Fetch live market\nstate from the Market Data API's REST snapshot endpoint and real-time transport from\n`websocket_cpp`. This preview exists only in Kairos staging and returns\n404 when disabled.\n","tags":["Perpetuals"],"x-kairos-auth":"public","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","security":[],"servers":[{"url":"https://staging-data.kairos.trade","description":"Staging preview only"}],"parameters":[{"name":"venue","in":"query","required":false,"description":"Restrict discovery to one canonical venue.","schema":{"$ref":"#/components/schemas/PerpetualVenue"}}],"responses":{"200":{"description":"Canonical perpetual instrument records.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PerpetualInstrument"}}}}},"404":{"description":"The staging preview is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"},"example":{"detail":"Perpetual metadata is disabled"}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"502":{"description":"Venue metadata was unavailable or failed strict validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"},"example":{"detail":"Perpetual venue metadata is unavailable or invalid"}}}}}}},"/markets/active":{"get":{"operationId":"listActiveMarkets","summary":"Enumerate active markets for a provider (MMC cursor page)","description":"Returns one cursor-paginated page of the active-market snapshot for a single provider. If the upstream listing is unavailable or the lookup otherwise fails, the route responds `503 Service Unavailable` (\"Active market snapshot is temporarily unavailable\") rather than fabricating an empty page — this can also happen for an exchange whose listing isn't ready yet. The response echoes back the validated/lowercased `provider` and a `source` field. `next_cursor` is opaque — pass it back verbatim as `cursor` to fetch the next page; `has_more: false` / `next_cursor: null` marks the last page.\n","tags":["Markets"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"200/minute","x-kairos-bucket":"market_data","parameters":[{"name":"provider","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":64,"default":"polymarket"},"description":"Provider/exchange id. Must be an active, known provider or the request is rejected with 400.\n","example":"polymarket"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":250,"default":100},"example":100},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":512},"description":"Opaque pagination cursor from a previous page's `next_cursor`."}],"responses":{"200":{"description":"One page of active markets for the provider.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketsActivePageResponse"}}}},"400":{"description":"Unknown/inactive provider.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Invalid provider: foobar"}}}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"503":{"description":"No active-market listing is ready for this provider yet, or the upstream lookup failed.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Active market snapshot is temporarily unavailable"}}}}}}}}},"/markets/details":{"post":{"operationId":"getMarketDetails","summary":"Fetch market details (name/category/status/ids)","description":"Looks up market details for the given `(market_id, provider_id)` pairs. The `market_id` in the request is matched against `market_id` OR `condition_id` OR `token_id` — any identifier works. Markets with an empty/missing `market_id` in the request are silently dropped before querying. Unmatched markets are simply absent from the response — there is no `found: false` sentinel here (contrast with `/markets/metadata/batch`). Hard cap: 500 markets per request.\n","tags":["Markets"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketsDetailsRequest"},"example":{"markets":[{"market_id":"KXBTC15M-26JUL221600-00","provider_id":1},{"market_id":"0x1234abcd...ef","provider_id":2}]}}}},"responses":{"200":{"description":"Matched market details (unmatched inputs are simply omitted).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketsDetailsResponse"}}}},"400":{"description":"Missing/empty `markets` array, or more than 500 markets requested.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"413":{"$ref":"#/components/responses/DataPayloadTooLarge"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Query failed.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Failed to fetch details"}}}}}}}}},"/markets/batch-prices":{"post":{"operationId":"batchFetchMarketPrices","summary":"Batch fetch current prices for multiple markets","description":"Fetches current price/volume/liquidity for up to 300 `(market_id, provider_id)` pairs. Results are cached briefly. Every reference must contain a non-empty market identifier and a known integer `provider_id`; an invalid reference rejects the request. A provider with no batch-price support is skipped (its markets are simply absent from the response, not an error). Response is a flat object keyed by the requested `market_id` — no top-level wrapper.\n","tags":["Markets"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"200/minute","x-kairos-bucket":"market_data","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketsBatchPricesRequest"},"example":{"markets":[{"market_id":"KXBTC15M-26JUL221600-00","provider_id":1},{"market_id":"0x1234abcd...ef","provider_id":2}]}}}},"responses":{"200":{"description":"Prices keyed by the requested `market_id`. Markets that could not be priced are simply absent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketsBatchPricesResponse"}}}},"400":{"description":"Missing/empty `markets` array, malformed market reference, unknown provider, or more than 300 markets requested.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"413":{"$ref":"#/components/responses/DataPayloadTooLarge"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Upstream provider fetch failed.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Batch fetch failed"}}}}}}}}},"/markets/metadata":{"get":{"operationId":"getMarketMetadata","summary":"Get full market metadata (rules, images, contract spec)","description":"Returns the full metadata document for one market. Resolution order: (1) a fast cached-metadata path — for Polymarket, a cache hit derives status/category/images from the cached upstream payload; (2) on a miss, falls back to a live provider-API fetch (Kalshi ticker + event metadata for images; Polymarket; Opinion; predict.fun) — this covers markets too new to be indexed yet (e.g. short-lived 15-minute crypto markets); (3) if the cache hit is non-null but carries **empty** `resolution_rules` (both `primary` and `secondary` blank), an extra provider-API call backfills `resolution_rules` (and `description` if also empty) without discarding the rest of the cached document. Only the cached-metadata path populates `condition_id`/`event_id`/`contract.settlement_ts` — the live provider-API fallback leaves those `null`. Finally, for `polymarket`/`predictfun`, a best-effort liquidity-rewards enrichment mutates `extra` in place (never blocks or fails the response): Polymarket sets `extra.clobRewards` (`[{rewardsDailyRate}]` or `[]`), `extra.rewardsMaxSpread`, `extra.rewardsMinSize`; predict.fun sets `extra.rewards.current` (an active reward-window object, or `null`). 404 only if no source has the market at all.\n","tags":["Markets"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","parameters":[{"name":"ticker","in":"query","required":true,"schema":{"type":"string"},"description":"Market ticker/ID.","example":"KXBTC15M-26JUL221600-00"},{"name":"provider","in":"query","required":true,"schema":{"type":"string"},"description":"kalshi or polymarket (also accepts any other active provider — e.g. predictfun, opinion — via the API fallback path).","example":"polymarket"}],"responses":{"200":{"description":"Full market metadata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketsMetadataResponse"}}}},"400":{"description":"Invalid/unknown provider, or a downstream `ValueError`.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"404":{"description":"Market not found in cached metadata or any provider API.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Market not found: KXFOO-99"}}}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Metadata lookup failed.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Metadata fetch failed"}}}}}}}}},"/markets/metadata/batch":{"post":{"operationId":"getMarketMetadataBatch","summary":"Batch fetch market metadata for multiple contracts","description":"Batch variant of `/markets/metadata`, capped at 100 contracts. Contracts whose provider is currently inactive are dropped before processing; if that empties the batch the response is `{}`. Looks up the rest via a cached-metadata fast path, with a fallback for cache misses and non-Polymarket providers. Unless `titles_only: true` is set, three further best-effort enrichment passes run (each independently swallows its own errors and never fails the request):\n  1. **API fallback** — any contract still `found: false` is retried\n     against the live provider API (same fallback `/markets/metadata`\n     uses), for markets too new to be indexed.\n  2. **Category inference** — markets with no `category` get one\n     inferred from Kalshi crypto ticker patterns or crypto keywords\n     in the title (bitcoin/ethereum/solana/xrp/\"btc \"/\"eth \"/\"sol \").\n  3. **Tag icon enrichment** — resolves market-to-platform-tag\n     mappings and assigns the highest-precedence tag's icon as\n     `tag_icon`; can also backfill `category` from the tag's slug\n     (crypto/politics/sports/esports/finance/tech/world).\nNote: Polymarket order-book liquidity-rewards enrichment (used by the single-market `/markets/metadata`) is intentionally **not** run here — this endpoint feeds list views where the rewards badge doesn't render.\n","tags":["Markets"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketsBatchMetadataRequest"},"example":{"contracts":[{"ticker":"KXBTC15M-26JUL221600-00","provider":"kalshi"},{"ticker":"0x1234abcd...ef","provider":"polymarket"}],"titles_only":false}}}},"responses":{"200":{"description":"Metadata items keyed by the requested ticker. Every requested ticker (whose provider wasn't hidden) appears, with `found: false` for unresolved ones.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketsBatchMetadataResponse"}}}},"400":{"description":"Missing/empty `contracts` array, or more than 100 contracts requested.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"413":{"$ref":"#/components/responses/DataPayloadTooLarge"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Metadata lookup failed.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Batch fetch failed"}}}}}}}}},"/markets/outcomes":{"get":{"operationId":"getMarketOutcomes","summary":"Get all sibling outcomes for the event containing a market","description":"Returns every outcome in the event that `market_id` belongs to (for multi-outcome / grouped markets), served from a cache of pre-computed event/market lookups. Any identifier (market_id, token_id, condition_id, symbol, slug) is mapped to its `event_id`; if that misses, falls back to the per-market entry's embedded `event_id`, then to deriving a Kalshi-style event ticker by stripping the last `-SEGMENT` off `market_id` (e.g. `KXPGATOUR-THGI26-SSTR` → `KXPGATOUR-THGI26`). If no event can be resolved at all and `provider=polymarket`, falls back to a live Polymarket lookup — guarding against a known upstream quirk that can silently return an unrelated default result instead of an empty list. Non-Polymarket providers get no such fallback — an unresolvable event returns the empty shape. `all_ids` (comma-separated, ≤200 ids, each ≤128 chars) additionally resolves `event_groups` — a map from every supplied id (and every sibling id discovered while resolving events) to its `event_id`, used by the frontend to dedupe sibling contracts across dropdowns. **Price scale note:** unlike most of this API, outcome prices here are returned as a **0–1 decimal probability**, not the platform's usual 0–100 cents scale.\n","tags":["Markets"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","parameters":[{"name":"market_id","in":"query","required":true,"schema":{"type":"string","maxLength":128},"description":"Market ID / ticker / token_id / slug. Non-empty after trimming, ≤128 chars.","example":"KXPGATOUR-THGI26-SSTR"},{"name":"provider","in":"query","required":true,"schema":{"type":"string"},"description":"Provider name; must be an active provider.","example":"kalshi"},{"name":"all_ids","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated list of additional contract ids (≤200 items, each ≤128 chars) to resolve into `event_groups` for dropdown deduplication."}],"responses":{"200":{"description":"Event outcomes for the market, or the empty shape if no event could be resolved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketsOutcomesResponse"}}}},"400":{"description":"Invalid/unknown provider, empty `market_id`, an oversized identifier, or too many `all_ids`.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/markets/crypto":{"get":{"operationId":"getCryptoMarkets","summary":"Get 5m/15m/1h crypto up-or-down prediction markets for a window","description":"Returns the crypto up/down markets (BTC/ETH/SOL/XRP on Kalshi + Polymarket, plus DOGE/HYPE/BNB and predict.fun on some intervals) for one time window. `window_offset` shifts by whole windows of the chosen `interval` (0=current, -1=previous, +1=next, etc.). Responses are cached so UI \"previous/next window\" navigation doesn't repeatedly re-fetch from upstream. Past windows are immutable and cached longer; future/current windows are refreshed more often since they're still filling in. A window is only cached once it has enough data from both venues; past windows relax that requirement for Kalshi, since Kalshi removes settled contracts from its API. **Price scale note:** `price` is a raw **0–1 decimal** probability read/derived directly from each venue's order book — NOT the platform's usual 0–100 cents scale.\n","tags":["Markets"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"200/minute","x-kairos-bucket":"market_data","parameters":[{"name":"interval","in":"query","required":false,"schema":{"type":"string","enum":["5m","15m","1h"],"default":"15m"},"example":"15m"},{"name":"window_offset","in":"query","required":false,"schema":{"type":"integer","minimum":-10000,"maximum":10000,"default":0},"description":"Number of windows from current (0=now, -1=previous, +1=next). Bounded so an absurd offset is rejected with 422 rather than overflowing the window arithmetic.","example":0}],"responses":{"200":{"description":"Crypto markets for the requested window.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketsCryptoResponse"}}}},"400":{"description":"Invalid `interval` (must be 5m, 15m, or 1h).","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Invalid interval: 30m. Must be one of: ['15m', '1h', '5m']"}}}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Upstream fetch (Kalshi/Polymarket/predict.fun) failed.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Failed to fetch crypto markets"}}}}}}}}},"/markets/crypto/oracle-history":{"get":{"operationId":"getOraclePriceHistory","summary":"Get historical oracle (spot) prices for crypto chart pre-population","description":"Returns per-symbol resolution-price history over `[end_ms - minutes*60*1000, end_ms]`\n(`end_ms` defaults to now; clamped to now if a future value is given). Each request\nnames **one** oracle `source` so settlement feeds are never mixed. Symbols must be\nthe wire ids for that source (bare `btc-usd` for Binance;\n`btc-usd-polymarket-chainlink`, `btc-usd-kalshi-cfb`, `btc-usd-hyperliquid-mark`,\nor a `-polymarket-twap30`/`-polymarket-twap60` suffix for venue series). When\n`source` is omitted, it is inferred from the wire symbols — mixed sources are\nrejected. Only Binance gaps may be backfilled from Binance REST; every other\nsource returns an empty array for a missing span. Responses are cached briefly.\nThe internal store can lag real time by a few seconds.\n","tags":["Markets"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"200/minute","x-kairos-bucket":"market_data","parameters":[{"name":"symbols","in":"query","required":true,"schema":{"type":"string"},"description":"Comma-separated, case-insensitive wire symbols for a single source.\nLogical assets are btc-usd, eth-usd, sol-usd, xrp-usd, doge-usd, hype-usd,\nbnb-usd; venue sources append their suffix (for example\n`btc-usd-polymarket-chainlink`). Kalshi CF Benchmarks supports BTC, ETH, SOL, XRP, DOGE, HYPE, and BNB.\n","example":"btc-usd"},{"name":"source","in":"query","required":false,"schema":{"type":"string","enum":["binance","hyperliquid_mark","kalshi_cfbenchmarks","polymarket_chainlink","polymarket_twap30","polymarket_twap60"]},"description":"Resolution feed identity. Omit only for legacy clients that encode the\nsource in the wire symbol; new callers should pass it explicitly.\n","example":"binance"},{"name":"minutes","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":1440,"default":10},"description":"Minutes of history to fetch (max 24h)."},{"name":"full_resolution","in":"query","required":false,"schema":{"type":"boolean","default":false},"description":"Return raw 1-second history without downsampling."},{"name":"end_ms","in":"query","required":false,"schema":{"type":"integer"},"description":"End timestamp in epoch ms (clamped to server \"now\"). Omit for the live edge. Used for chunked/paginated backward loading."}],"responses":{"200":{"description":"Price-history points per symbol, sorted ascending by timestamp.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketsOracleHistoryResponse"}}}},"400":{"description":"No symbols given, unknown `source`, symbols that do not belong to the\nrequested source, mixed-source inference failure, or a non-positive\n`end_ms`.\n","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/markets/crypto/ptb":{"get":{"operationId":"getCryptoPriceToBeat","summary":"Get Price-to-Beat (PTB) values for crypto up/down contracts","description":"Returns the oracle price captured at the start of each requested window\n(\"the price to beat\"), nested `{window: {symbol: {...}}}`. `windows` is\ncomma-separated; each must be one of `1m, 5m, 15m, 1h, 4h, 1d`. Window\nstarts align to clean ET boundaries. Only **window** PTB sources are\naccepted: `binance`, `kalshi_cfbenchmarks`, `polymarket_twap30`,\n`polymarket_twap60`. Contract-tick series (`polymarket_chainlink`,\n`hyperliquid_mark`) are not window benchmarks and are rejected. When\n`source` is omitted, the server picks Binance or a legacy TWAP series\nfrom the `oracle_twap_feed` flag. Binance may fall back to an external\nmarket-data source for missing symbols; venue series do not. Symbols\nwith no resolvable price for a window are **omitted** (not `null`).\n","tags":["Markets"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"200/minute","x-kairos-bucket":"market_data","parameters":[{"name":"windows","in":"query","required":false,"schema":{"type":"string","default":"15m"},"description":"Comma-separated windows. Each must be one of 1m, 5m, 15m, 1h, 4h, 1d.","example":"5m,15m"},{"name":"source","in":"query","required":false,"schema":{"type":"string","enum":["binance","kalshi_cfbenchmarks","polymarket_twap30","polymarket_twap60"]},"description":"ET-aligned window source. New market clients should pass this\nexplicitly. TWAP30 remains for legacy 1m overlays.\n","example":"kalshi_cfbenchmarks"}],"responses":{"200":{"description":"PTB values nested by window then symbol.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketsPtbResponse"}}}},"400":{"description":"No windows given, or an invalid window value.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Invalid window: 30m. Must be one of: ['15m', '1d', '1h', '1m', '4h', '5m']"}}}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Oracle store/source lookup failed for one or more windows.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Failed to fetch price-to-beat values"}}}}}}}}},"/markets/equity/snapshot":{"get":{"operationId":"getEquitySnapshot","summary":"Get last-known prices for equity/forex/commodity symbols","description":"Returns last-known prices for a fixed allow-list of stock, ETF, forex, and commodity symbols (e.g. AAPL, SPY, EURUSD, XAUUSD), mapped internally to their upstream tickers. A request is served straight from cache only if every requested symbol is already cached. Otherwise, missing symbols are fetched and merged into the existing cache, and only the newly-fetched subset is returned with `cached: false` — symbols already warm in the cache are NOT re-included in that response body (the endpoint returns either the full cached hit set with `cached: true`, or just the freshly-fetched symbols with `cached: false`, never a merge of both in one response). Returns prices even when markets are closed (uses the last regular-session print) so the UI is never blank outside trading hours. **Auth differs from every other endpoint in this router**: this accepts a first-party session JWT (`Authorization: Bearer ...`) or an internal admin credential — it does **not** accept the `X-Client-Id`/`X-Api-Key`/ `X-Api-Secret` API-key headers that the rest of `/markets/*` accepts.\n","tags":["Markets"],"x-kairos-auth":"session","x-kairos-rate-limit":"200/minute","x-kairos-bucket":"market_data","security":[{"bearerAuth":[]}],"parameters":[{"name":"symbols","in":"query","required":true,"schema":{"type":"string"},"description":"Comma-separated symbols (case-insensitive, upper-cased server-side). Must be a subset of: AAPL, TSLA, MSFT, GOOGL, AMZN, META, NVDA, NFLX, PLTR, OPEN, RKLB, ABNB, COIN, HOOD, SPY, QQQ, EWY, VXX, EURUSD, GBPUSD, USDCAD, USDJPY, USDKRW, XAUUSD, XAGUSD, WTI, CC, NGD.\n","example":"AAPL,TSLA,EURUSD"}],"responses":{"200":{"description":"Last-known prices for the requested symbols.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketsEquitySnapshotResponse"}}}},"400":{"description":"No symbols given, or a symbol outside the supported allow-list.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Invalid symbol: DOGEUSD. Must be one of: [...]"}}}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/candles":{"get":{"operationId":"getCandles","summary":"Fetch OHLCV candles for a single contract","tags":["Candles"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"200/minute","x-kairos-bucket":"candles","description":"Returns OHLCV candle bars for one `(provider, contract_id, outcome)` series over `[start, end)`, bucketed at `timeframe_seconds`.\n\n**Auth**: accepts an admin secret (`X-Admin-Secret`), an API key (`X-Client-Id` + `X-Api-Key` + `X-Api-Secret`), or a first-party JWT (`Authorization: Bearer` or `turnkey_session_jwt` cookie). No API-key scope is required for this endpoint — any valid credential can read candles.\n\n**Cache / data source**: responses may be served from cache unless `rebuild=true`, which always bypasses the cache and reconstructs candles from the underlying trade/candle data, then repopulates the cache. `rebuild=true` is for callers that explicitly want fresh data regardless of what's cached.\n\n**Validation** (all return 400): unknown `provider` (must exist in the live provider registry — currently includes `kalshi`, `polymarket`, `dome`, `opinion`); empty or >128-char `contract_id`; `timeframe_seconds` not one of the fixed allowed values; unparseable `start`/`end` (ISO 8601); or `end <= start`. An `outcome` index invalid for the given provider/contract (e.g. an out-of-range index on a binary market) also returns 400.\n","parameters":[{"name":"provider","in":"query","required":true,"schema":{"type":"string"},"description":"Venue identifier, matched case-insensitively against the live provider registry (e.g. `kalshi`, `polymarket`). Unknown providers return 400.\n","example":"kalshi"},{"name":"contract_id","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":128},"description":"Provider-specific contract/market/token identifier. Whitespace is trimmed; empty or >128 chars returns 400.","example":"KXPRESPOLAND-24-DT"},{"name":"timeframe_seconds","in":"query","required":true,"schema":{"type":"integer","enum":[1,60,300,900,3600,14400,86400]},"description":"Candle bucket width in seconds. FastAPI first enforces `>= 1`; the handler then rejects any value not exactly one of `[1, 60, 300, 900, 3600, 14400, 86400]` (1s, 1m, 5m, 15m, 1h, 4h, 1d) with 400.\n","example":60},{"name":"start","in":"query","required":true,"schema":{"type":"string","format":"date-time"},"description":"Window start, ISO 8601 (any offset; normalized to UTC internally). Unparseable values return 400.","example":"2026-07-21T00:00:00Z"},{"name":"end","in":"query","required":true,"schema":{"type":"string","format":"date-time"},"description":"Window end, ISO 8601. Must be strictly after `start` (400 otherwise).","example":"2026-07-22T00:00:00Z"},{"name":"outcome","in":"query","required":false,"schema":{"type":["integer","null"],"minimum":0},"description":"Zero-based outcome index for multi-outcome markets. Omitted/null is treated as `0` (the first/primary outcome). An index invalid for the contract's outcome count returns 400.\n"},{"name":"rebuild","in":"query","required":false,"schema":{"type":"boolean","default":false},"description":"When true, bypasses the cache and forces a fresh fetch/rebuild."}],"responses":{"200":{"description":"OHLCV series for the requested window.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CandlesSeriesResponse"}}}},"400":{"description":"Invalid request — unknown provider, invalid/missing contract_id, disallowed timeframe_seconds, unparseable or out-of-order start/end, or an outcome index invalid for this contract.\n","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Invalid timeframe. Must be one of: [1, 60, 300, 900, 3600, 14400, 86400]"}}}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Candle fetch failed (cache or ClickHouse read error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/candles/batch":{"post":{"operationId":"postCandlesBatch","summary":"Fetch candles for up to 200 contracts in one request","tags":["Candles"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"200/minute","x-kairos-bucket":"candles","description":"Batched form of `GET /candles`. Accepts up to 200 per-contract requests and returns results in the same order, index-tagged.\n\n**Auth**: same as `GET /candles` — `require_auth_or_admin_or_apikey`, no scope required.\n\n**Cache / data source**: each item is checked against the cache in parallel first; only uncached items are fetched fresh. Items with `rebuild: true` always skip the cache regardless of a hit.\n\n**Fail-fast, partial-failure semantics**: an empty series for an item is returned as `\"candles\": []` (not an error) — the client renders an empty chart and expects later bars via the live WebSocket feed; there is no server-side ingestion retry. If fetching fails for some items but others were already served from cache, those cached items are still returned; every failed item instead gets `\"candles\": [], \"error\": \"<message>\"` so the client can distinguish a genuine failure from a legitimately empty window. Only if **no** item could be served at all does the whole request fail with 500.\n\n**Validation** (all 400): body must contain a JSON array under `requests` (or the legacy alias `items`); the array must be non-empty and at most 200 items; each item must be an object with `provider`, `contract_id`, `timeframe_seconds`, `start`, and `end` present; the same per-field checks as `GET /candles` apply per item (unknown provider, invalid contract_id, disallowed timeframe, unparseable/out-of-order timestamps, negative or non-integer `outcome`).\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CandlesBatchRequest"},"example":{"requests":[{"provider":"kalshi","contract_id":"KXPRESPOLAND-24-DT","timeframe_seconds":60,"start":"2026-07-21T00:00:00Z","end":"2026-07-22T00:00:00Z","outcome":0,"rebuild":false},{"provider":"polymarket","contract_id":"0xabc123...token","timeframe_seconds":3600,"start":"2026-07-15T00:00:00Z","end":"2026-07-22T00:00:00Z"}]}}}},"responses":{"200":{"description":"Index-ordered results, one per request item. A failed item carries an `error` string and an empty `candles` array instead of a 5xx for the whole batch.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CandlesBatchResponse"}}}},"400":{"description":"Malformed batch — `requests` missing/not an array/empty/over 200 items, or a specific item is malformed (missing required field, unknown provider, invalid contract_id, disallowed timeframe_seconds, unparseable/out-of-order start/end, non-integer or negative outcome).\n","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Max 200 requests"}}}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"413":{"$ref":"#/components/responses/DataPayloadTooLarge"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Every request in the batch failed; nothing could be served from cache.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/trades/kalshi":{"get":{"operationId":"getKalshiTrades","summary":"Proxy Kalshi's public trade-history API for one market","tags":["Trades"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:read","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","description":"Thin proxy over Kalshi's `GET /trade-api/v2/markets/trades`, with a short-lived cache and Kairos-computed metrics appended.\n\n**Auth**: requires the `trade:read` scope. Admin-secret and first-party JWT callers are always allowed through with no scope check. API-key callers must have `trade:read` in their credential's `scopes` array, or this returns 403 (`API key missing required scope: trade:read`). API-key callers are additionally gated by the `kalshi` platform's API access being enabled — if it's disabled, this returns 403 regardless of scope.\n\n**Cache / data source**: responses are cached briefly to de-duplicate bursts of identical requests, then fall through to the live Kalshi API at `https://api.elections.kalshi.com/trade-api/v2/markets/trades`. A Kalshi HTTP error (non-2xx or network failure) returns 502.\n\n**Response shape**: the raw Kalshi API response object is returned unmodified except for an injected `metrics` field, computed over the *locally time-filtered* trades (filtering by `min_ts`/`max_ts` is done client-side against each trade's `created_time`, since Kalshi's API does not filter server-side beyond `min_ts`/`max_ts` passthrough). The metrics window is fixed at 24 hours regardless of the requested `min_ts`/`max_ts` span.\n","parameters":[{"name":"ticker","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":128},"description":"Kalshi market ticker. Empty or >128 chars returns 400.","example":"KXPRESPOLAND-24-DT"},{"name":"min_ts","in":"query","required":false,"schema":{"type":"integer","nullable":true},"description":"Inclusive lower bound, Unix seconds, applied both to the upstream request and to client-side filtering by `created_time`."},{"name":"max_ts","in":"query","required":false,"schema":{"type":"integer","nullable":true},"description":"Inclusive upper bound, Unix seconds, applied both to the upstream request and to client-side filtering by `created_time`."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":100},"description":"Max trades returned, passed through to the upstream Kalshi API."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","maxLength":512,"nullable":true},"description":"Upstream pagination cursor from a previous response. Blank strings are treated as absent; >512 chars returns 400."}],"responses":{"200":{"description":"Raw Kalshi trade-history payload plus a computed `metrics` object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TradesKalshiResponse"}}}},"400":{"description":"Missing/empty `ticker`, or `cursor` too long.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"description":"API-key credential lacks the `trade:read` scope, or API access to the `kalshi` platform is currently disabled.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"API key missing required scope: trade:read"}}}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"502":{"description":"Kalshi's upstream trade-history API returned an error or was unreachable.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Kalshi API error"}}}}}},"503":{"description":"Per-venue API-access flag could not be read or validated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/trades/polymarket":{"get":{"operationId":"getPolymarketTrades","summary":"Proxy Polymarket's public trade-history API for one market","tags":["Trades"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:read","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","description":"Thin proxy over Polymarket's `GET https://data-api.polymarket.com/trades`, with a short-lived cache and Kairos-computed metrics appended.\n\n**Auth**: identical model to `/trades/kalshi` — requires the `trade:read` scope (admin/JWT bypass the scope check; API keys need `trade:read`), plus API access to the `polymarket` platform being enabled for API-key callers (403 if disabled).\n\n**Market resolution**: `market` may be a `0x`-prefixed condition ID (used as-is) or a market/token identifier that is resolved to a condition ID via a cached metadata lookup, then a direct database lookup, and finally a live Polymarket API lookup as a last resort. If resolution never yields a value starting with `0x`, the endpoint short-circuits and returns `{\"trades\": [], \"metrics\": {}}` **without** calling the upstream trades API.\n\n**Cache / data source**: responses are cached briefly to de-duplicate bursts of identical requests. On a miss, calls the Polymarket Data API with the resolved `condition_id` as `market`. A non-2xx/network error returns 502.\n\n**Response shape**: `trades` is the raw upstream trade array (client-side re-filtered by `after` against each trade's `timestamp` as a safety net). `metrics` is computed over the filtered trades with a metrics window fixed at 24 hours; it groups trade value by outcome identifier (`asset_id`/`outcome`/`token_id`) and keeps only the top 2 outcomes by volume as an approximate outcome_0/outcome_1 split — this is an approximation, not a guaranteed yes/no mapping.\n","parameters":[{"name":"market","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":128},"description":"Polymarket condition ID (`0x...`), market ID, or token ID. Non-`0x` values are resolved to a condition ID before querying upstream. Empty or >128 chars returns 400.\n","example":"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcd"},{"name":"after","in":"query","required":false,"schema":{"type":"integer","nullable":true},"description":"Inclusive lower bound, Unix seconds. Passed to upstream and re-applied client-side against each trade's `timestamp`."},{"name":"before","in":"query","required":false,"schema":{"type":"integer","nullable":true},"description":"Inclusive upper bound, Unix seconds, passed to the upstream API."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":100},"description":"Max trades returned, passed through to the upstream Polymarket API."}],"responses":{"200":{"description":"Raw Polymarket trades plus computed `metrics`. `metrics` is `{}` when the `market` value could not be resolved to a condition ID (no upstream call was made).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TradesPolymarketResponse"}}}},"400":{"description":"Missing/empty `market`.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"description":"API-key credential lacks the `trade:read` scope, or API access to the `polymarket` platform is currently disabled.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"502":{"description":"Polymarket's upstream trade-history API returned an error or was unreachable.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Polymarket API error"}}}}}},"503":{"description":"Per-venue API-access flag could not be read or validated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/trades/history":{"get":{"operationId":"getTradeHistory","summary":"Fetch normalized, Kairos-ingested trade history for a contract","tags":["Trades"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:read","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","description":"Returns normalized trade history for a single `(provider, contract_id)` from Kairos's own ingested trade store. Unlike `/trades/kalshi` and `/trades/polymarket`, this is not a live upstream proxy — it serves from data Kairos has already ingested, so coverage depends on how much history has been backfilled/streamed for that contract.\n\n**Auth**: requires the `trade:read` scope (admin/JWT bypass the scope check; API keys need `trade:read`), plus API access to the requested provider being enabled for API-key callers — 403 if disabled.\n\n**Data source / staleness**: reads ingested trade rows for the window `[before - window_seconds, before]` (or ending \"now\" if `before` is omitted). If `trigger_ingest=true` and the data is missing or stale, an ingestion job is kicked off for that contract and reported via the `indexing` flag in the response — the returned trades may still be incomplete for that call.\n","parameters":[{"name":"provider","in":"query","required":true,"schema":{"type":"string"},"description":"Venue identifier, matched case-insensitively against the live provider registry.","example":"kalshi"},{"name":"contract_id","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":128},"description":"Provider-specific contract identifier. Empty or >128 chars returns 400."},{"name":"window_seconds","in":"query","required":false,"schema":{"type":"integer","minimum":3600,"maximum":86400,"default":86400},"description":"Lookback window in seconds, ending at `before` (or now). Clamped to [3600, 86400] (1 hour to 24 hours)."},{"name":"before","in":"query","required":false,"schema":{"type":"integer","nullable":true},"description":"Unix-seconds cursor — return trades at or before this timestamp. Omit to end the window at \"now\"."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"nullable":true},"description":"Max trades returned. If omitted, the service applies its own internal default."},{"name":"trigger_ingest","in":"query","required":false,"schema":{"type":"boolean","default":false},"description":"If true, kicks off ingestion when data for this window is stale or missing, rather than just returning what's already stored."}],"responses":{"200":{"description":"Normalized trade rows for the window plus pagination/coverage metadata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TradesHistoryResponse"}}}},"400":{"description":"Invalid trade history request (unknown provider, invalid contract_id, or a request the service itself rejected as malformed).","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Invalid trade history request"}}}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"description":"API-key credential lacks the `trade:read` scope, or API access to the requested provider is currently disabled.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Trade history fetch failed on the backend.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Trade history fetch failed"}}}}}},"503":{"description":"Per-venue API-access flag could not be read or validated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/trades/metrics":{"get":{"operationId":"getTradeMetrics","summary":"Fetch volume and outcome-pressure metrics for a contract","tags":["Trades"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:read","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","description":"Returns aggregate trade metrics (volume, per-outcome volume split, trade count) for a single `(provider, contract_id)` over a lookback window, computed from Kairos's own ingested trade store — same data source as `/trades/history`, not a live upstream proxy. There is no buy/sell-side breakdown: metrics are split by outcome (`outcome_0`/`outcome_1`) rather than trade direction, since trade direction cannot be reliably determined from exchange data for all providers.\n\n**Auth**: requires the `trade:read` scope (admin/JWT bypass the scope check; API keys need `trade:read`), plus API access to the requested provider being enabled for API-key callers — 403 if disabled.\n\n**Staleness**: if `trigger_ingest=true` and ingestion coverage for the window is low, the service triggers a backfill/ingestion job and reports it via the `indexing` flag on the returned metrics object.\n","parameters":[{"name":"provider","in":"query","required":true,"schema":{"type":"string"},"description":"Venue identifier, matched case-insensitively against the live provider registry.","example":"kalshi"},{"name":"contract_id","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":128},"description":"Provider-specific contract identifier. Empty or >128 chars returns 400."},{"name":"window_seconds","in":"query","required":false,"schema":{"type":"integer","minimum":3600,"maximum":86400,"default":86400},"description":"Lookback window in seconds, ending \"now\". Clamped to [3600, 86400] (1 hour to 24 hours)."},{"name":"trigger_ingest","in":"query","required":false,"schema":{"type":"boolean","default":false},"description":"If true, triggers ingestion when coverage for this window is low, rather than computing metrics from partial/stale data alone."}],"responses":{"200":{"description":"Aggregate volume/pressure metrics for the window.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TradesMetricsResponse"}}}},"400":{"description":"Invalid trade metrics request (unknown provider, invalid contract_id, or a request the service itself rejected as malformed).","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Invalid trade metrics request"}}}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"description":"API-key credential lacks the `trade:read` scope, or API access to the requested provider is currently disabled.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Trade metrics fetch failed on the backend.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Trade metrics fetch failed"}}}}}},"503":{"description":"Per-venue API-access flag could not be read or validated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/trader-stats/pnl-history/{wallet_address}":{"get":{"operationId":"getTraderPnlHistory","summary":"Get a trader's realized-PnL time series","tags":["Trader Analytics"],"x-kairos-auth":"public","x-kairos-rate-limit":"10/minute","x-kairos-bucket":"heavy","security":[],"description":"Returns a time-series of realized PnL data points for charting a trader's PnL progression over the requested window, bucketed by `time_range`. The wallet address is validated as either an Ethereum (0x...) address (Polymarket/Opinion/predictfun) or a Solana base58 address (Kalshi) and normalized (lower-cased for EVM) before lookup; if it matches neither format the request is rejected with 400. `provider` is optional — if omitted, the provider is auto-detected from the address format; pass it explicitly to disambiguate EVM venues other than Polymarket. Provider values are validated against the live set of active providers rather than a fixed enum, so an inactive/unknown id is rejected with 400. Responses are cacheable at the edge and briefly cached server-side. **Public endpoint — no authentication required.** Rate-limited per client IP (X-Forwarded-For, falling back to the socket peer) at 30 requests/minute, independent of the global 100/minute default.\n","parameters":[{"name":"wallet_address","in":"path","required":true,"schema":{"type":"string"},"description":"Ethereum (0x-prefixed, 42 chars) or Solana (base58) wallet address. Normalized (EVM lower-cased) before use. Returns 400 if it matches neither format.\n","example":"0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"},{"name":"time_range","in":"query","required":false,"schema":{"type":"string","enum":["1D","1W","1M","ALL"],"default":"ALL"},"description":"Time window for the PnL series."},{"name":"provider","in":"query","required":false,"schema":{"type":"string"},"description":"Provider override (e.g. polymarket, kalshi, opinion, predictfun). Validated against the live active-provider registry. Auto-detected from address format when omitted.\n","example":"polymarket"}],"responses":{"200":{"description":"PnL time series for the wallet and time range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TraderPnlHistoryResponse"}}}},"400":{"description":"Malformed wallet address or unrecognized provider."},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Unexpected error fetching PnL history."}}}},"/trader-stats/summary/{wallet_address}":{"get":{"operationId":"getTraderSummary","summary":"Get a trader's performance stats without the position list","tags":["Trader Analytics"],"x-kairos-auth":"public","x-kairos-rate-limit":"10/minute","x-kairos-bucket":"heavy","security":[],"description":"Returns the stats a trader profile's header and stats panel show (PnL, ROI, win rate, volume, position counts, positions_value) without the position list, so they render before the positions page. The figures match `performance` on `/trader-stats/positions`. `performance` is null for a venue whose stats come only from the full positions build; read them from `/trader-stats/positions` there. **Public endpoint — no authentication required** (trader data is public). Rate-limited per client IP. Responses are cacheable at the edge.\n","parameters":[{"name":"wallet_address","in":"path","required":true,"schema":{"type":"string"},"description":"Ethereum or Solana wallet address.","example":"0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"},{"name":"include_redeemable","in":"query","required":false,"schema":{"type":"boolean","default":false},"description":"Count resolved positions still waiting for redemption."},{"name":"provider","in":"query","required":false,"schema":{"type":"string"},"description":"Provider override (polymarket, kalshi, opinion, predictfun). Auto-detected if omitted.","example":"predictfun"}],"responses":{"200":{"description":"Performance stats over the wallet's full book.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TraderSummaryResponse"}}}},"400":{"description":"Malformed wallet address or unrecognized provider."},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Unexpected error fetching trader summary."}}}},"/trader-stats/analysis/{wallet_address}":{"get":{"operationId":"getTraderAnalysis","summary":"Get a trader's PnL windows, win/loss, ROI distribution and daily PnL calendar","tags":["Trader Analytics"],"x-kairos-auth":"public","x-kairos-rate-limit":"10/minute","x-kairos-bucket":"heavy","security":[],"description":"Returns the trader profile's analysis panel: realized PnL and buy count over the 1D, 7D, 30D and ALL windows (whole UTC days ending today; 1D is today only), lifetime winning/losing closed positions and win rate, the distribution of closed positions by ROI (realized PnL over the cost bought for the position), and daily realized PnL for the last 90 UTC days that have activity. Only venues whose trades flow through the lot allocator (Predict.fun) are supported; for any other venue `supported` is false and no figures are returned. **Public endpoint — no authentication required** (trader data is public). Rate-limited per client IP. Cached per wallet for up to a minute.\n","parameters":[{"name":"wallet_address","in":"path","required":true,"schema":{"type":"string"},"description":"Ethereum or Solana wallet address.","example":"0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"},{"name":"provider","in":"query","required":false,"schema":{"type":"string"},"description":"Provider override (polymarket, kalshi, opinion, predictfun). Auto-detected if omitted.","example":"predictfun"}],"responses":{"200":{"description":"The analysis panel, or `supported` false for a venue without allocator data.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TraderAnalysisResponse"}}}},"400":{"description":"Malformed wallet address or unrecognized provider."},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Unexpected error fetching trader analysis."}}}},"/trader-stats/positions/{wallet_address}":{"get":{"operationId":"getTraderPositions","summary":"Get a trader's open/closed positions and derived performance stats","tags":["Trader Analytics"],"x-kairos-auth":"public","x-kairos-rate-limit":"10/minute","x-kairos-bucket":"heavy","security":[],"description":"Returns a trader's positions plus performance metrics computed from a full-inventory scan — served independently of the unified profile so the Positions tab and stats panel can load on their own fetch. Positions are filtered by `status` (open/closed/all), sorted by current value (largest first), and paginated via `limit`/`offset`; `has_more` indicates another page exists in the filtered set. Market names/icons are resolved only for the returned page, so wallets holding a very large number of positions don't blow past internal query limits. `performance` (win rate, total PnL, volume, positions_value, etc.) is always computed over the wallet's FULL inventory, not just the returned page. `include_redeemable` additionally includes resolved positions still awaiting on-chain redemption. **Public endpoint — no authentication required** (trader data is public). Rate-limited per client IP at 30 requests/minute. Responses are cacheable at the edge.\n","parameters":[{"name":"wallet_address","in":"path","required":true,"schema":{"type":"string"},"description":"Ethereum or Solana wallet address (validated/normalized as above).","example":"0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"},{"name":"include_redeemable","in":"query","required":false,"schema":{"type":"boolean","default":false},"description":"Include resolved positions still waiting for redemption."},{"name":"provider","in":"query","required":false,"schema":{"type":"string"},"description":"Provider override (polymarket, kalshi, opinion, predictfun). Auto-detected if omitted.","example":"polymarket"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000,"default":200},"description":"Max positions to return per page (sorted by value, largest first)."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0},"description":"Number of positions to skip (pagination)."},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["all","open","closed"],"default":"all"},"description":"Filter the returned page by position status."}],"responses":{"200":{"description":"Positions page plus full-inventory performance stats.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TraderPositionsResponse"}}}},"400":{"description":"Malformed wallet address or unrecognized provider."},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Unexpected error fetching trader positions."}}}},"/trader-stats/trades/{wallet_address}":{"get":{"operationId":"getTraderTrades","summary":"Get a trader's recent trade/fill history","tags":["Trader Analytics"],"x-kairos-auth":"public","x-kairos-rate-limit":"10/minute","x-kairos-bucket":"heavy","security":[],"description":"Returns a paginated page of a wallet's fills, served independently of the full profile as the fast path for the History tab. Reads only trade rows (plus market-title enrichment) — it deliberately skips the heavier positions/inventory and PnL-summary computations used by the full profile build. Each trade carries the realized PnL booked by that fill (populated for SELLs; BUYs report null/0). **Public endpoint — no authentication required** (trader data is public). Rate-limited per client IP at 60 requests/minute. Responses are cacheable at the edge.\n","parameters":[{"name":"wallet_address","in":"path","required":true,"schema":{"type":"string"},"description":"Ethereum or Solana wallet address (validated/normalized as above).","example":"0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"},{"name":"trade_limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":50},"description":"Max trades to fetch per page."},{"name":"trade_offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0},"description":"Offset for trade pagination."},{"name":"provider","in":"query","required":false,"schema":{"type":"string"},"description":"Provider override (polymarket, kalshi, opinion, predictfun). Auto-detected if omitted.","example":"polymarket"}],"responses":{"200":{"description":"Page of the wallet's trade history.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TraderRecentTradesResponse"}}}},"400":{"description":"Malformed wallet address or unrecognized provider."},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Unexpected error fetching trader trades."}}}},"/top-holders":{"get":{"operationId":"getTopHolders","summary":"Get top token holders for one or more markets","tags":["Trader Analytics"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"10/minute","x-kairos-bucket":"heavy","description":"Fetches the largest holders of each outcome token for the given market IDs from the provider-specific upstream source, including Polymarket's data-api and Predict.fun's public GraphQL API. `market` accepts a comma-separated list (up to 50 IDs, each capped at 128 chars); Predict.fun requires numeric market IDs. An empty or missing value is rejected with 400. Results are grouped per token/outcome and include public profile fields (username, verification badge, avatar) alongside the held `amount`. This is a live upstream read, not served from a local cache; a downstream 4xx/5xx from the provider surfaces as 404 (market/token not found) or 502 (provider error). **Auth**: accepts an admin secret (`X-Admin-Secret`), an API key (`X-Client-Id` + `X-Api-Key` + `X-Api-Secret`), or a first-party JWT (Authorization header or `turnkey_session_jwt` cookie). No API-key scope is required — any valid credential can read holders. Rate-limited by the `heavy` group at 10 requests/minute, the same budget the `/trader-stats` endpoints carry, because every call is a live upstream provider read.\n","parameters":[{"name":"market","in":"query","required":true,"schema":{"type":"string"},"description":"Comma-separated list of market/condition IDs (max 50 items, 128 chars each).","example":"0xabc123,0xdef456"},{"name":"provider","in":"query","required":false,"schema":{"type":"string","default":"polymarket"},"description":"Provider name, validated against the active provider registry. Examples include polymarket and predictfun."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":20,"default":20},"description":"Maximum number of holders to return per token."},{"name":"minBalance","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":999999,"default":1},"description":"Minimum token balance a holder must have to be included."}],"responses":{"200":{"description":"Top holders per requested token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TraderTopHoldersResponse"}}}},"400":{"description":"Missing/empty `market`, too many market IDs, an oversized market ID, an invalid `provider`, or an invalid request accepted by the provider client.\n"},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"404":{"description":"Market or token not found by the upstream provider."},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"502":{"description":"Upstream provider error fetching top holders."}}}},"/search-traders":{"get":{"operationId":"searchTraders","summary":"Search for a trader's public profile by wallet address","tags":["Trader Analytics"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"10/minute","x-kairos-bucket":"heavy","description":"Fetches public profile information (display name, pseudonym, bio, avatar, X/Twitter handle, verification badge, associated user IDs) for a wallet address from the upstream provider. Provider is auto-detected from address format when omitted — Ethereum (0x...) addresses default to Polymarket and Solana addresses default to Kalshi; auto-detection maps **every** 0x address to Polymarket, so other EVM venues (Opinion, predictfun, ...) must pass `provider` explicitly. `provider`, when given, is validated against the central provider registry shared with `/trader-stats`; any registered provider is accepted, and non-Polymarket venues currently receive a synthetic (service-generated) profile rather than a live upstream fetch. This is a live provider read, not served from cached data. **Auth**: accepts an admin secret (`X-Admin-Secret`), an API key (`X-Client-Id` + `X-Api-Key` + `X-Api-Secret`), or a first-party JWT (Authorization header or `turnkey_session_jwt` cookie). No API-key scope is required — any valid credential can read a public profile. Rate-limited by the `heavy` group at 10 requests/minute, matching `/trader-stats`, because every call is a live upstream provider read.\n","parameters":[{"name":"address","in":"query","required":true,"schema":{"type":"string"},"description":"Wallet address to search for (any non-empty string; trimmed).","example":"0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"},{"name":"provider","in":"query","required":false,"schema":{"type":"string"},"description":"Provider override. Auto-detected from address format (0x -> polymarket, Solana -> kalshi) if omitted.\n","example":"polymarket"}],"responses":{"200":{"description":"Trader search result. `profile` is null if no profile was found; `error` carries a provider-supplied message in that case.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TraderSearchResponse"}}}},"400":{"description":"Missing/blank `address` or an invalid `provider`."},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"502":{"description":"Upstream provider error searching for the trader."}}}},"/pnl/providers":{"get":{"operationId":"listPnlProviders","summary":"List available PnL providers","tags":["PnL"],"x-kairos-auth":"api-key","x-kairos-scope":"position:read","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","description":"Returns the set of providers the merged-PnL pipeline knows about, each with a human-readable description. For API-key callers, the set is additionally filtered to providers whose API access is currently enabled — JWT-user and admin callers are exempt from this check. No endpoint-specific rate limit; the global default of 100 requests/minute applies.\n","parameters":[],"responses":{"200":{"description":"List of available PnL providers.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PnlProviderInfo"}},"example":[{"name":"polymarket","description":"Polymarket prediction market PnL"},{"name":"hyperliquid","description":"hyperliquid PnL provider"}]}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"description":"API key is missing the `position:read` scope, or the platform's API access is disabled via feature flag."},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"503":{"description":"Per-venue API-access flag could not be read or validated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/pnl/{user_id}":{"get":{"operationId":"getUserPnl","summary":"Get merged PnL data for a user across one or more wallets/providers","tags":["PnL"],"x-kairos-auth":"api-key","x-kairos-scope":"position:read","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","description":"Fetches and merges PnL records/summary across the wallets supplied for this user via the general-purpose PnL path — not the real-time pipeline used by `/pnl/hover` and `/pnl/wallet-totals`. At least one of `polymarket_wallet`, `kalshi_wallet`, or `hyperliquid_wallet` is required — the request is rejected with 400 if none are supplied. `polymarket_wallet` and `hyperliquid_wallet` must be valid Ethereum addresses (normalized/lower-cased); `kalshi_wallet` is treated as an opaque user-id string (Kalshi is a CEX with internal, non-address identifiers). `provider` (repeatable query param, up to 16 items) filters the merged result down to specific provider(s); when omitted, all providers implied by the supplied wallets are used. Kalshi and Hyperliquid PnL are served only through this aggregate endpoint; neither is available from `/pnl/hover` or `/pnl/wallet-totals`. Hyperliquid open-position and unrealized PnL use HIP-4 balances and mark prices, while realized PnL and fee fields are currently zero. The caller-supplied `{user_id}` MUST match the authenticated principal's own id (JWT `sub`, or `user_id` for API-key auth) — mismatches are rejected with 403; this endpoint can only ever return the caller's own PnL. `limit`/`offset` paginate the merged record list; internally up to `min(limit + offset, 1000)` records per provider are fetched, so `total`/`has_more` in the response describe the page relative to that ceiling, not an authoritative full-history count. For API-key callers, each requested provider must additionally have its API access enabled; JWT/admin callers are exempt.\n","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","maxLength":128},"description":"Kairos internal user id. Must match the authenticated caller's own id.","example":"usr_9f3c2a1b"},{"name":"polymarket_wallet","in":"query","required":false,"schema":{"type":"string","maxLength":128},"description":"Polymarket (Ethereum) wallet address.","example":"0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"},{"name":"kalshi_wallet","in":"query","required":false,"schema":{"type":"string","maxLength":128},"description":"Kalshi user ID (opaque internal identifier, not a wallet address)."},{"name":"hyperliquid_wallet","in":"query","required":false,"schema":{"type":"string","maxLength":128},"description":"Hyperliquid EVM address (HIP-4 positions key off this same deposit/trade address).","example":"0x2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c"},{"name":"provider","in":"query","required":false,"style":"form","explode":true,"schema":{"type":"array","maxItems":16,"items":{"type":"string"}},"description":"Filter merged PnL to these provider(s). Repeatable query param.","example":["polymarket","hyperliquid"]},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":500},"description":"Records per page."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":10000,"default":0},"description":"Records to skip."}],"responses":{"200":{"description":"Merged PnL summary and records for the user.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PnlUserPnlResponse"}}}},"400":{"description":"Missing/oversized `user_id`, an invalid wallet address, an invalid `provider` filter, or no wallet address supplied at all.\n"},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"description":"The `user_id` does not match the authenticated caller, the API key is missing the `position:read` scope, or platform API access is disabled."},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"503":{"description":"Per-venue API-access flag could not be read or validated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/pnl/hover/{provider_id}/{wallet}/{contract_id}":{"get":{"operationId":"getWalletMarketPnl","summary":"Live per-market PnL for a (wallet, market) pair","tags":["PnL"],"x-kairos-auth":"api-key","x-kairos-scope":"position:read","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","description":"Live \"hover\" PnL for a single wallet on a single market, sourced from a fast real-time pipeline — quick regardless of how far back the wallet's earliest trade goes. Returns one row per token held on the contract (e.g. YES + NO for a binary market) plus aggregate totals across those tokens. Only providers on this pipeline are supported: `polymarket`, `opinion`, `predictfun`; `kalshi` and `hyperliquid` are rejected with 400 and must use `/pnl/{user_id}`. When the underlying pipeline is disabled (e.g. while a backfill is ramping), the endpoint returns a well-formed zero/empty payload (`tokens: []`, all totals `0.0`) instead of erroring, matching the \"no data\" state the frontend already renders gracefully. For API-key callers, the requested provider must additionally have its API access enabled.\n","parameters":[{"name":"provider_id","in":"path","required":true,"schema":{"type":"string","maxLength":32,"enum":["polymarket","opinion","predictfun"]},"description":"MV-pipeline provider id. `kalshi`, `hyperliquid`, and any other registered-but-non-MV provider are rejected with 400.","example":"polymarket"},{"name":"wallet","in":"path","required":true,"schema":{"type":"string","maxLength":128},"description":"Wallet address (opaque string; trimmed, length-checked — not format-validated at this layer).","example":"0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"},{"name":"contract_id","in":"path","required":true,"schema":{"type":"string","maxLength":128},"description":"Market/condition (contract) id.","example":"0xabc123condition"}],"responses":{"200":{"description":"Per-token PnL rows and aggregate totals for the wallet on this market. Returns an empty/zeroed payload (not a 404) when the underlying PnL pipeline is disabled.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PnlWalletMarketPnlResponse"}}}},"400":{"description":"Missing/oversized `wallet`/`contract_id`, or `provider_id` is not a valid MV-pipeline provider (e.g. kalshi or hyperliquid)."},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"description":"API key missing `position:read` scope, or platform API access disabled for this provider."},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Unexpected error fetching hover PnL."},"503":{"description":"Per-venue API-access flag could not be read or validated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/pnl/wallet-totals/{provider_id}/{wallet}":{"get":{"operationId":"getWalletTotals","summary":"Wallet-wide PnL totals across all markets","tags":["PnL"],"x-kairos-auth":"api-key","x-kairos-scope":"position:read","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","description":"Wallet-wide PnL totals aggregated across every market the wallet has touched, refreshed hourly — a fast point lookup rather than a live scan. For real-time per-market freshness on a single active market, use `/pnl/hover/{provider_id}/{wallet}/{contract_id}` instead. Same provider restriction as `/pnl/hover`: only `polymarket`, `opinion`, `predictfun` are accepted; `kalshi` and `hyperliquid` are rejected with 400 and must use `/pnl/{user_id}`. When the underlying pipeline is disabled, returns a well-formed zero payload (`market_count: 0`, `token_count: 0`, all dollar totals `0.0`, `snapshot_ts` set to the current time) instead of erroring. For API-key callers, the requested provider must additionally have its API access enabled.\n","parameters":[{"name":"provider_id","in":"path","required":true,"schema":{"type":"string","maxLength":32,"enum":["polymarket","opinion","predictfun"]},"description":"MV-pipeline provider id. `kalshi` and `hyperliquid` are rejected with 400.","example":"polymarket"},{"name":"wallet","in":"path","required":true,"schema":{"type":"string","maxLength":128},"description":"Wallet address (opaque string; trimmed, length-checked — not format-validated at this layer).","example":"0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b"}],"responses":{"200":{"description":"Wallet-wide PnL totals as of the last hourly snapshot. Returns a zeroed payload (not a 404) when the underlying PnL pipeline is disabled.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PnlWalletTotalsResponse"}}}},"400":{"description":"Missing/oversized `wallet`, or `provider_id` is not a valid MV-pipeline provider (e.g. kalshi or hyperliquid)."},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"description":"API key missing `position:read` scope, or platform API access disabled for this provider."},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Unexpected error fetching wallet totals."},"503":{"description":"Per-venue API-access flag could not be read or validated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/search/markets":{"get":{"operationId":"searchMarkets","summary":"Search markets by text query","description":"Unified market search. Results are post-filtered to drop rows whose provider is currently inactive.","tags":["Search"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"60/minute","x-kairos-bucket":"search","parameters":[{"name":"q","in":"query","required":true,"description":"Search query text.","schema":{"type":"string","minLength":1,"maxLength":200},"example":"trump 2028"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"provider","in":"query","description":"Filter by provider id (e.g. kalshi, polymarket, predictfun, opinion, dome). Validated dynamically against active providers; 400 if unrecognized. `provider_id` is accepted as a legacy alias — if both are given they must match or the request 400s.","schema":{"type":"string"},"example":"polymarket"},{"name":"provider_id","in":"query","description":"Legacy alias for `provider`. See `provider`.","schema":{"type":"string"}},{"name":"include_expired","in":"query","schema":{"type":"boolean","default":false}},{"name":"statuses","in":"query","description":"Repeatable market-status filter (e.g. open, closed, settled). Passed through unvalidated.","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true},{"name":"categories","in":"query","description":"Repeatable category filter. Passed through unvalidated.","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true},{"name":"tags","in":"query","description":"Repeatable platform-tag-slug filter (query param name is `tags`; internally `platform_tags`).","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true}],"responses":{"200":{"description":"Search results, visibility-filtered by active provider.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchMarketsResponse"}}}},"400":{"description":"Empty/whitespace-only `q`, or invalid `provider`/`provider_id` (unknown id, or mismatch between the two).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchBadRequestError"}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Search backend query failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/search/markets-and-events":{"get":{"operationId":"searchMarketsAndEvents","summary":"Search both markets and events with combined relevance scoring","description":"Runs a market search and/or event search depending on `type`, tags event rows with `\"type\": \"event\"`, merges both result sets, sorts by `relevance_score` descending, and truncates to `limit`. Same provider-visibility post-filter as `/search/markets`.","tags":["Search"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"60/minute","x-kairos-bucket":"search","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":200}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"provider","in":"query","schema":{"type":"string"}},{"name":"provider_id","in":"query","description":"Legacy alias for `provider`.","schema":{"type":"string"}},{"name":"type","in":"query","description":"Which result set(s) to include.","schema":{"type":"string","enum":["market","event","both"],"default":"both"}},{"name":"include_expired","in":"query","schema":{"type":"boolean","default":false}},{"name":"statuses","in":"query","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true},{"name":"categories","in":"query","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true},{"name":"tags","in":"query","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true}],"responses":{"200":{"description":"Combined market + event results, sorted by relevance descending.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchMarketsAndEventsResponse"}}}},"400":{"description":"Empty `q`, invalid provider, or `type` not one of market/event/both.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchBadRequestError"}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Search backend query failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/search/simple":{"get":{"operationId":"searchSimple","summary":"Fast simple search for navbar/autocomplete overlay","description":"Primary search endpoint behind the web navbar overlay. Supports sort/offset/pagination and optional server-side event grouping (`group_results`), then applies provider-visibility filtering. When `group_results=true`, best-effort enrichment (never fails the request) adds `classifiedGroups`/`absorbedMarketIds` (interactive ladder groups) and `correlations` (a cross-venue similar-market rail, similarity above a threshold, capped at 4 counterparts per result market). When `group_results=false`, only `correlations` is attached.","tags":["Search"],"x-kairos-auth":"public","security":[],"x-kairos-rate-limit":"60/minute","x-kairos-bucket":"search","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":200}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":1000,"default":20}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0}},{"name":"provider","in":"query","schema":{"type":"string"}},{"name":"provider_id","in":"query","description":"Legacy alias for `provider`.","schema":{"type":"string"}},{"name":"tags","in":"query","description":"Repeatable platform-tag-slug filter.","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true},{"name":"statuses","in":"query","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true},{"name":"sort_by","in":"query","schema":{"type":"string","enum":["relevance","volume_24h","newest","ending_soon"],"default":"relevance"}},{"name":"sort_order","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"include_total","in":"query","description":"Include a best-effort `meta.total` (falls back to the filtered count if any rows were dropped by visibility filtering).","schema":{"type":"boolean","default":false}},{"name":"include_expired","in":"query","schema":{"type":"boolean","default":false}},{"name":"group_results","in":"query","description":"Group markets by event server-side, producing `groups`/`singles` in the response.","schema":{"type":"boolean","default":true}}],"responses":{"200":{"description":"Simple-search result envelope, optionally grouped and enriched with classified ladders + cross-venue correlations.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchSimpleResponse"}}}},"400":{"description":"Empty `q`, invalid provider, or invalid `sort_by`/`sort_order`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchBadRequestError"}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Search backend query failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/search/resolve-url":{"get":{"operationId":"searchResolveUrl","summary":"Resolve a pasted Polymarket/Kalshi/predict.fun market or event URL","description":"Parses `url` against known host patterns — polymarket.com `/event/<slug>/<market-slug>` (market) or `/event/<slug>` (event) or legacy `/market/<slug>`; kalshi.com ticker/event_id/market_id path segments; predict.fun equivalents. Unrecognized hosts or unmatched paths return an empty `/search/simple`-shaped body (200) so the frontend can fall back to a normal text search rather than erroring. On a match, resolves the market/event (for an event URL, every outcome market in that event) and returns the same `{results, groups?, singles?, meta}` shape as `/search/simple`, enriched with `correlations` only (ladder classification is skipped — a resolved URL is already a single market/event, not a broad set to collapse).","tags":["Search"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"60/minute","x-kairos-bucket":"search","parameters":[{"name":"url","in":"query","required":true,"description":"Raw pasted URL (scheme optional — `https://` is prepended if missing) or arbitrary text.","schema":{"type":"string","minLength":1,"maxLength":1000},"example":"https://polymarket.com/event/will-trump-win-2028/yes"}],"responses":{"200":{"description":"Resolved market/event in `/search/simple` shape, or an empty body if the URL wasn't recognized.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchSimpleResponse"}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"URL resolution failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/search/suggest":{"get":{"operationId":"searchSuggest","summary":"Autocomplete suggestions for search-as-you-type","tags":["Search"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"60/minute","x-kairos-bucket":"search","description":"Returns autocomplete suggestions. Not provider-visibility filtered.","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2,"maxLength":100}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":50,"default":10}},{"name":"provider","in":"query","description":"Filter suggestions by provider id.","schema":{"type":"string"}}],"responses":{"200":{"description":"Autocomplete suggestions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchSuggestResponse"}}}},"400":{"description":"Empty `q` or invalid provider.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchBadRequestError"}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Suggestion query failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/search/screener":{"get":{"operationId":"searchScreener","summary":"Find markets by what their book and tape are doing","tags":["Search"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"30/minute","x-kairos-bucket":"screener","description":"Screens the whole live catalogue by spread, resting depth, rolling volume, open interest and price, rather than by text. Numbers are live from the market-vitals cache (sub-second book and tape), joined to the discover cache for titles; a market the discover cache has never seen is still returned, under its id, with `hasMetadata: false`.\n\nPrices and spread are in cents, money in dollars. **At least one metric bound is required** — an unfiltered screen is a ranking, not a screen, and is rejected with 400.\n\n`spread` is `ask - bid` on one outcome, and a book with only one side cannot be crossed, so it has no spread: any query bounding spread only matches markets quoting both sides. `two_sided` asks for that explicitly without bounding the width. Bounds are compared on one canonical price scale, so a threshold means the same width on every venue whatever scale that venue publishes.","parameters":[{"name":"min_spread","in":"query","description":"Cents. Only two-sided books can satisfy a spread bound.","schema":{"type":"number","minimum":0,"maximum":100}},{"name":"max_spread","in":"query","description":"Cents.","schema":{"type":"number","minimum":0,"maximum":100}},{"name":"min_price","in":"query","description":"Cents, on the matched outcome's mid.","schema":{"type":"number","minimum":0,"maximum":100}},{"name":"max_price","in":"query","description":"Cents, on the matched outcome's mid.","schema":{"type":"number","minimum":0,"maximum":100}},{"name":"min_volume_24h","in":"query","description":"Dollars traded in the rolling 24h window.","schema":{"type":"number","minimum":0}},{"name":"min_volume_1h","in":"query","description":"Dollars traded in the rolling 1h window.","schema":{"type":"number","minimum":0}},{"name":"min_liquidity","in":"query","description":"Dollars resting across every outcome, both sides.","schema":{"type":"number","minimum":0}},{"name":"max_liquidity","in":"query","description":"Dollars resting across every outcome, both sides. Pair with a volume floor to find thin books carrying heavy flow.","schema":{"type":"number","minimum":0}},{"name":"min_outcome_liquidity","in":"query","description":"Dollars resting on the matched outcome alone, both sides.","schema":{"type":"number","minimum":0}},{"name":"min_open_interest","in":"query","description":"Contracts outstanding.","schema":{"type":"number","minimum":0}},{"name":"two_sided","in":"query","description":"Only markets quoting a bid and an ask. A settled market has no quotes at all, which would otherwise read as infinitely thin.","schema":{"type":"boolean","default":false}},{"name":"max_book_age_minutes","in":"query","description":"Drop markets whose top of book has not been published within this many minutes. Settled and delisted markets stop being streamed, so their frozen book would otherwise screen as a live quote — on a live catalogue that is more than half the records. `0` removes the bound and returns them.\n\nThe timestamp is the streamer's publish clock, so this detects \"nothing is publishing this market any more\", not \"the venue's book went quiet\": a wedged venue connection re-publishing a cached book still stamps now.","schema":{"type":"number","minimum":0,"default":10}},{"name":"provider","in":"query","description":"Repeatable. Restricts the screen to these venues.","schema":{"type":"array","items":{"type":"string"}}},{"name":"sort","in":"query","schema":{"type":"string","default":"volume_24h","enum":["volume_24h","volume_1h","spread_desc","spread_asc","liquidity","open_interest"]}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":50}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"maximum":10000,"default":0}}],"responses":{"200":{"description":"A page of matching markets, most relevant to the chosen sort first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreenerResponse"}}}},"400":{"description":"No metric bound given, an inverted range, an unknown provider or sort, or a query the vitals service refused.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchBadRequestError"}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Screener query failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/search/screener/presets":{"get":{"operationId":"searchScreenerPresets","summary":"One-click screens the screener offers","tags":["Search"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"30/minute","x-kairos-bucket":"screener","description":"Named parameter sets for common screens, so a client does not hardcode thresholds. Each preset's `params` are query parameters for `/search/screener`. Also lists the accepted `sort` values.","responses":{"200":{"description":"Available presets and sorts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreenerPresetsResponse"}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/api/markets/discover/v2":{"get":{"operationId":"discoverMarketsV2","summary":"Paginated, filtered, event-grouped market list for the Discover page","description":"Returns a paginated, filtered, sorted, event-grouped market list for the Discover page, with server-side filtering (provider, category incl. alias remapping, free-text search, price/volume bounds, explicit market-id list, platform-tag/subcategory intersection, expiration window) and sorting. Most filter combinations are cacheable; free-text or bounded (price/volume) requests always run fresh and are not cached. Tag/subcategory filters that resolve to zero markets short-circuit to an empty payload; tag-filtered requests that would otherwise come back empty also get a best-effort fallback lookup so populated subtopics never render empty.","tags":["Discover"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"60/minute","x-kairos-bucket":"discover","parameters":[{"name":"offset","in":"query","description":"Bounded by the candidate window the feed is assembled from; `total` is derived from that same window, so real pagination never reaches the cap.","schema":{"type":"integer","minimum":0,"maximum":5000,"default":0}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"sort_by","in":"query","schema":{"type":"string","enum":["volume","volume_24h","volume_1h","price","newest","liquidity","rewards"],"default":"volume_1h"}},{"name":"sort_order","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"provider","in":"query","description":"Provider id or `all`. Validated dynamically against currently active providers.","schema":{"type":"string"},"example":"kalshi"},{"name":"category","in":"query","description":"Topic category. Display aliases `elections`, `geopolitics`, `economy`, `climate & science` are remapped server-side to `politics`, `world`, `finance`, `weather` respectively before filtering.","schema":{"type":"string","enum":["all","politics","crypto","sports","esports","finance","tech","world","weather","elections","geopolitics","culture","economy","climate & science"]}},{"name":"search","in":"query","description":"Free-text filter. Disables response caching for this request.","schema":{"type":"string","maxLength":128}},{"name":"min_price","in":"query","description":"Inclusive lower bound, in cents (0-100). Disables response caching.","schema":{"type":"number","minimum":0,"maximum":100}},{"name":"max_price","in":"query","description":"Inclusive upper bound, in cents (0-100). Disables response caching.","schema":{"type":"number","minimum":0,"maximum":100}},{"name":"min_volume_1h","in":"query","description":"Minimum hourly volume, matched against volume1hRank so a market whose hour nobody reports is judged on its day rather than dropped. Disables response caching.","schema":{"type":"number","minimum":0}},{"name":"market_ids","in":"query","description":"Comma-separated market ids (max 500 items, 128 chars each). Disables response caching.","schema":{"type":"string"},"example":"KXPRES-28,0x1234abcd"},{"name":"tag_ids","in":"query","description":"Comma-separated platform-tag UUIDs, AND-intersected against `market_ids`/`tag_slug` (max 50). Disables response caching.","schema":{"type":"string"}},{"name":"tag_slug","in":"query","description":"Single platform-tag slug (alternative to `tag_ids`).","schema":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{0,63}$"}},{"name":"subcategory","in":"query","description":"Subcategory (subtopic) tag slug, intersected with any other filters. Also accepts the tournament sentinel `__tournament:<uuid>` emitted by the web bracket UI, which short-circuits to an empty markets payload rather than 400ing.","schema":{"type":"string"}},{"name":"expiration_after","in":"query","description":"ISO-8601 datetime lower bound (inclusive) on market expiration.","schema":{"type":"string","format":"date-time"}},{"name":"expiration_before","in":"query","description":"ISO-8601 datetime upper bound (exclusive) on market expiration.","schema":{"type":"string","format":"date-time"}},{"name":"zipper","in":"query","description":"Interleave/zip results across providers instead of a flat sort.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Paginated market list (event-grouped where applicable).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscoverV2Response"}}}},"400":{"description":"Invalid sort_by/sort_order/category/provider, invalid tag_slug/subcategory slug, invalid expiration ISO date, or oversized market_ids/tag_ids list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscoverBadRequestError"}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Discover assembly failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}},"503":{"description":"Discover data store is not ready.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/api/markets/discover/v2/search-index":{"get":{"operationId":"discoverSearchIndex","summary":"Compact full market list for client-side instant search","description":"Returns every valid market (grouped by event) in a compact shape for the client to index locally. Responses are cached to keep this fast after the first build.","tags":["Discover"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"60/minute","x-kairos-bucket":"discover","parameters":[],"responses":{"200":{"description":"Full compact market index.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscoverSearchIndexResponse"}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Search-index rebuild failed on a cache miss.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/api/markets/discover/v2/ticker":{"get":{"operationId":"discoverTicker","summary":"Top markets for the discover page ticker bar","description":"Returns the top markets for the discover page ticker bar. Responses are cached briefly.","tags":["Discover"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"60/minute","x-kairos-bucket":"discover","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":60,"default":30}}],"responses":{"200":{"description":"Ticker-bar market list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscoverTickerResponse"}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Ticker rebuild failed on a cache miss.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/api/markets/discover/v2/breaking":{"get":{"operationId":"discoverBreaking","summary":"Market pulse — latest trending movers for the Pulse rail","description":"Interleaves biggest 24h price movers, top 1h-volume, and top 24h-volume markets (deduped). Responses are cached briefly.","tags":["Discover"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"60/minute","x-kairos-bucket":"discover","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":50,"default":25}}],"responses":{"200":{"description":"Breaking/trending market list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscoverBreakingResponse"}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Pulse rebuild failed on a cache miss.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/api/markets/discover/v2/expiring":{"get":{"operationId":"discoverExpiring","summary":"Markets expiring soonest, with actionable prices","description":"Returns markets expiring soonest, with actionable prices. Responses are cached briefly.","tags":["Discover"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"60/minute","x-kairos-bucket":"discover","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":20,"default":7}}],"responses":{"200":{"description":"Soonest-expiring market list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscoverExpiringResponse"}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Expiring-markets rebuild failed on a cache miss.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/api/markets/discover/v2/subcategories":{"get":{"operationId":"discoverSubcategories","summary":"Available subcategories (subtopics) for a topic category, with live market counts","description":"Resolves the topic tag's child subtopic tags together with live market counts, drops zero-count subtopics, and sorts by count descending. Responses are cached, including empty results.","tags":["Discover"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"60/minute","x-kairos-bucket":"discover","parameters":[{"name":"category","in":"query","required":true,"description":"Topic tag slug (e.g. crypto, sports, politics).","schema":{"type":"string"}}],"responses":{"200":{"description":"Subcategories with live market counts, sorted descending.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscoverSubcategoriesResponse"}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"503":{"description":"Subcategory index is missing or unreadable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/api/sports/kalshi-live":{"get":{"operationId":"discoverKalshiLiveSports","summary":"Kalshi sports events matched against live Polymarket games","description":"Returns cached Kalshi sports events and, if `games` is supplied, matches each `AWAY:HOME[:league]` triple's team codes (uppercased) against each event's ticker suffix (segment after the first `-`), grouping and sorting matches by total volume descending.","tags":["Discover"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"60/minute","x-kairos-bucket":"discover","parameters":[{"name":"games","in":"query","description":"Comma-separated list of `AWAY:HOME[:league]` team-code triples, e.g. `ATL:CLE:nba,DET:MIN:mlb`. When omitted, returns all cached events with an empty `matched` list.","schema":{"type":"string"},"example":"ATL:CLE:nba,DET:MIN:mlb"}],"responses":{"200":{"description":"Cached Kalshi sports events, optionally matched to requested games.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscoverKalshiLiveResponse"}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/api/markets/trending":{"get":{"operationId":"discoverTrendingMarkets","summary":"Trending markets by 1h volume","description":"Returns trending markets ranked by 1-hour volume.","tags":["Discover"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"60/minute","x-kairos-bucket":"discover","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":50,"default":10}},{"name":"provider","in":"query","description":"Provider id filter, or omit/`all` for every provider.","schema":{"type":"string"}}],"responses":{"200":{"description":"Trending markets ranked by 1h volume.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscoverTrendingResponse"}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Trending lookup failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/api/markets/trending/ws":{"get":{"operationId":"discoverTrendingMarketIds","summary":"Trending market ids for a live-price subscription","description":"Same ranking as `/api/markets/trending`, reduced to the fields a WebSocket subscriber needs to open a live-price stream (id, symbol, price, 24h volume). Not cached — every call reads the trending index directly.","tags":["Discover"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"60/minute","x-kairos-bucket":"discover","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":300,"default":20}},{"name":"provider","in":"query","description":"Provider id filter, or omit for every provider.","schema":{"type":"string"}}],"responses":{"200":{"description":"Trending market ids ranked by 1h volume.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscoverTrendingWsResponse"}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Trending lookup failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/providers/configs":{"get":{"operationId":"getProviderConfigs","summary":"List all active provider configurations","description":"Returns the minimal exchange-agnostic config needed by the UI for every currently active provider. Public endpoint — no auth headers are checked. No endpoint-specific rate limit; only the global default (100/minute) applies.","tags":["Providers"],"x-kairos-auth":"public","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","security":[],"parameters":[],"responses":{"200":{"description":"All active provider configs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProvidersConfigsResponse"}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/providers/api-access":{"get":{"operationId":"getProviderAPIAccess","summary":"List providers available to API-key callers","description":"Returns active providers whose API-access toggle is enabled. Public endpoint — no auth required. Operator reasons and other admin-only flag metadata are never returned. No endpoint-specific rate limit; only the global default (100/minute) applies.","tags":["Providers"],"x-kairos-auth":"public","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","security":[],"parameters":[],"responses":{"200":{"description":"Providers currently available through API-key authentication.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProvidersAPIAccessResponse"}}}},"429":{"$ref":"#/components/responses/DataRateLimited"},"503":{"description":"API-access flags could not be read or validated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/providers/configs/{provider_id}":{"get":{"operationId":"getProviderConfig","summary":"Get a single provider configuration by id","description":"Looks up one provider by id (case-insensitive, trimmed). Public endpoint — no auth required. No endpoint-specific rate limit; only the global default (100/minute) applies.","tags":["Providers"],"x-kairos-auth":"public","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","security":[],"parameters":[{"name":"provider_id","in":"path","required":true,"schema":{"type":"string"},"example":"hyperliquid"}],"responses":{"200":{"description":"The provider's configuration.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProvidersConfig"},"example":{"id":"hyperliquid","display_name":"Hyperliquid","icon_url":"https://imagedelivery.net/Hias1rvxalFDFzbgXzaBJg/cfe0d4ac-9597-415b-c186-a375bda5be00/public","chain_id":"hyperliquid","chain_name":"Hyperliquid","is_active":true,"supported_order_types":["limit","market"],"supports_walk_the_book":false,"supports_token_approval":false,"has_multi_token_markets":true,"auth_flow_type":"wallet_signature","token_id_format":"opaque","metadata_key_type":"marketId","numeric_id":9,"primary_color":"#97FCE4"}}}},"404":{"description":"No provider with this id is loaded/active.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProvidersNotFoundError"}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/sports/trending-matched":{"get":{"operationId":"getSportsTrendingMatched","summary":"Top live/upcoming sports markets matched across Polymarket and Kalshi","description":"Finds games that have BOTH a Polymarket and a Kalshi entry in the cross-venue sports match cache, drops any entry whose embedded date is older than yesterday (UTC), enriches each surviving Kalshi side with a live volume figure (`volume_1h` falling back to `volume_total`), sorts descending by that volume, and truncates to `limit`. Title and `tokenId` come from the cached match record itself, not from a live provider call. Responses are cacheable at the edge.","tags":["Sports"],"x-kairos-auth":"public","security":[],"x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","parameters":[{"name":"limit","in":"query","required":false,"description":"Maximum number of matched markets to return, sorted by Kalshi volume descending.","schema":{"type":"integer","minimum":1,"maximum":20,"default":5},"example":5}],"responses":{"200":{"description":"Trending cross-provider matched markets. Returns `{\"matches\": [], \"count\": 0}` immediately if the matching cache is empty.","headers":{"Cache-Control":{"schema":{"type":"string","example":"public, max-age=30, s-maxage=60"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SportsTrendingMatchedResponse"}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/sports/matching-markets":{"get":{"operationId":"getSportsMatchingMarkets","summary":"Cross-platform matching-market records for a set of market slugs","description":"Returns the cross-venue match record for each requested market slug, keyed by slug. Provider entries can include Polymarket, Kalshi, Predict.fun, and Hyperliquid. Slugs with no cross-venue match are silently omitted from the response object (not returned as null). When `live=true`, the Kalshi side of every matched entry is refreshed in-place with a live price (in cents / 100, 4 decimal places); a correction is applied when a stale price appears to be on the wrong side of a binary flip. Responses are cacheable, briefly when `live=true`, longer otherwise.","tags":["Sports"],"x-kairos-auth":"public","security":[],"x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","parameters":[{"name":"slugs","in":"query","required":true,"description":"Comma-separated list of market or matching slugs to look up. Include every slug carried by the discovery card because event slugs and per-outcome market slugs are distinct. Empty/blank entries are dropped.","schema":{"type":"string","minLength":1},"example":"nba-lal-bos-2026-01-15,nhl-tor-mtl-2026-01-15"},{"name":"live","in":"query","required":false,"description":"Pass the literal string \"true\" to refresh Kalshi prices before returning. Any other value, including omission, is treated as false.","schema":{"type":"string","enum":["true","false"]},"example":"true"}],"responses":{"200":{"description":"Map of slug to matching-market record. Returns `{}` if `slugs` parses to no non-empty entries.","headers":{"Cache-Control":{"schema":{"type":"string","example":"public, max-age=30, s-maxage=60"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SportsMatchingMarketsResponse"}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/sports/kalshi-filters":{"get":{"operationId":"getSportsKalshiFilters","summary":"Kalshi Sport -> Competition -> Scope taxonomy","description":"Returns the upstream Kalshi taxonomy payload verbatim (no normalization). Used to drive Kalshi sport/competition/scope filter UI.","tags":["Sports"],"x-kairos-auth":"public","security":[],"x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","responses":{"200":{"description":"Raw Kalshi filters-by-sport taxonomy.","headers":{"Cache-Control":{"schema":{"type":"string","example":"public, max-age=21600"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SportsKalshiFiltersResponse"}}}},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/sports/live-events":{"get":{"operationId":"getSportsLiveEvents","summary":"Active sports games with urgency scores and cross-provider prices","description":"Returns active game IDs plus, when the provider indexing gate has `predictfun` enabled, open Predict.fun fixtures inside their scheduled start/end window. The gate is re-checked on every request (including cache hits); a gate lookup failure excludes Predict.fun (fail closed) without dropping Polymarket/Kalshi rows. Hydrates current state, drops ended games idle for more than a few minutes, and computes an `urgencyScore`/`stale` flag per game. Enriches each event with cached primary-market data, falling back to a live provider lookup when the cache is cold. Resolves each event's matching Polymarket/Kalshi slug and attaches the corresponding cross-venue match record into `matchingMarkets`. If no matched entry anywhere carries a Kalshi side, synthesizes one from a team-name-matching fallback lookup. Results are sorted by most-recent update, then by `urgencyScore` descending. Served from a shared server cache rebuilt about every 5 seconds while the endpoint is in use and never more than 60 seconds old; the `Age` header gives the seconds since the body was built.","tags":["Sports"],"x-kairos-auth":"public","security":[],"x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","parameters":[{"name":"include_markets","in":"query","required":false,"description":"When true, includes the full markets list (not just the primary market) for every event.","schema":{"type":"boolean","default":false},"example":false}],"responses":{"200":{"description":"Active games ordered by recency then urgency, plus their matched cross-provider markets.","headers":{"Cache-Control":{"schema":{"type":"string","example":"public, max-age=10, s-maxage=10"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SportsLiveEventsResponse"}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/sports/event-markets":{"get":{"operationId":"getSportsEventMarkets","summary":"All markets for a single sports event","description":"Resolves markets for one game. If `game_id` is given, tries a cached lookup first, falling back to a live Polymarket lookup on a miss. If only `slug` is given, first resolves the event's `gameId` via a live Polymarket lookup, then performs the same event-markets lookup. Returns an empty markets list if neither parameter resolves to an event. At least one of `game_id` or `slug` should be supplied; both are optional and unvalidated strings.","tags":["Sports"],"x-kairos-auth":"public","security":[],"x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","parameters":[{"name":"game_id","in":"query","required":false,"description":"Upstream sports game ID to look up directly.","schema":{"type":"string"},"example":"12345678"},{"name":"slug","in":"query","required":false,"description":"Polymarket market slug used to discover the event's gameId when game_id is not known.","schema":{"type":"string"},"example":"nba-lal-bos-2026-01-15-lal"}],"responses":{"200":{"description":"Markets for the resolved event. `gameId` echoes the resolved id, or falls back to the raw `game_id`/`slug` query value (\"unknown\" if neither was supplied) when nothing could be resolved.","headers":{"Cache-Control":{"schema":{"type":"string","example":"public, max-age=10, s-maxage=10"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SportsEventMarketsResponse"}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/sports/kalshi-live-games":{"get":{"operationId":"getSportsKalshiLiveGames","summary":"Currently-live Kalshi sports games (NHL/NBA/MLB/NFL+UFL/soccer/esports)","description":"Returns every Kalshi milestone currently live for the supported milestone types (hockey_tournament, basketball_game, baseball_game, football_game, soccer_tournament_multi_leg, esports_match), built into moneyline/spread/total market blocks from Kalshi's trading API. \"Live\" means started recently or starting soon, per Kalshi's live-data status. Responses are cacheable.","tags":["Sports"],"x-kairos-auth":"public","security":[],"x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","responses":{"200":{"description":"Currently live Kalshi sports games with team, score/clock, and market data.","headers":{"Cache-Control":{"schema":{"type":"string","example":"public, max-age=240"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SportsKalshiLiveGamesResponse"}}}},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/sports/poly-kalshi-pairings":{"get":{"operationId":"getSportsPolyKalshiPairings","summary":"Live game pairings between Polymarket and Kalshi","description":"Cross-references currently-live Polymarket games against currently-live Kalshi games, grouped by a Kalshi-league -> Polymarket-sport crosswalk and matched via a multi-step heuristic (ticker-code match, team-name/alias substring match, derived-code prefix match, letter-subset fallback). **Price scale note:** Kalshi market prices in the `kalshiMarkets` array are rescaled to a 0-100 range (not the 0-1 scale used elsewhere in this API) to match a legacy response shape. Falls back to a recent last-known-good snapshot if the live Kalshi data is unavailable. Responses are cacheable.","tags":["Sports"],"x-kairos-auth":"public","security":[],"x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","responses":{"200":{"description":"Matched Polymarket <-> Kalshi live game pairs.","headers":{"Cache-Control":{"schema":{"type":"string","example":"public, max-age=10"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SportsPolyKalshiPairingsResponse"}}}},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/sports/catalog":{"get":{"operationId":"getSportsCatalog","summary":"Enriched catalog of sport categories and leagues","description":"Returns the curated sports taxonomy, including UEFA Europa League (`uel`) under soccer, with the provider series and tag identifiers clients need to request league events. Responses are cacheable.","tags":["Sports"],"x-kairos-auth":"public","security":[],"x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","responses":{"200":{"description":"Sport categories, each with its leagues and resolved Polymarket seriesId.","headers":{"Cache-Control":{"schema":{"type":"string","example":"public, max-age=3600, s-maxage=3600"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SportsCatalogResponse"}}}},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/sports/upcoming-events":{"get":{"operationId":"getSportsUpcomingEvents","summary":"Upcoming (not-yet-live) sports events within a time window","description":"Returns fixtures in the requested league set whose scheduled start falls within `windowHours`, together with primary markets, combo eligibility where available, and cross-venue matches for Polymarket, Kalshi, Predict.fun, and Hyperliquid. Provider-only fixtures may be returned as standalone events. Served from a shared server cache rebuilt about every minute; a copy is never more than two hours old for the default 720h/200 and 168h/12 windows, which are kept warm, or ten minutes old for any other window; the `Age` header gives the seconds since the body was built. `startFrom`/`startTo` return one slice of the window by start time, for loading a schedule a few days at a time. Returns 502 when no provider can supply data and 504 when a cold build runs out of time.","tags":["Sports"],"x-kairos-auth":"public","security":[],"x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","parameters":[{"name":"windowHours","in":"query","required":false,"description":"Look-ahead window, in hours, for upcoming game start times.","schema":{"type":"integer","minimum":1,"maximum":720,"default":24},"example":24},{"name":"category","in":"query","required":false,"description":"Sports-catalog category id (e.g. \"basketball\"). Expands to every league slug in that category and takes priority over league/series.","schema":{"type":"string"},"example":"basketball"},{"name":"league","in":"query","required":false,"description":"Single league slug filter (e.g. \"nba\"). Used only if category is not supplied or does not resolve.","schema":{"type":"string"},"example":"nba"},{"name":"series","in":"query","required":false,"description":"Comma-separated league slugs. Used only if neither category nor league resolves.","schema":{"type":"string"},"example":"nba,nhl"},{"name":"limitPerSeries","in":"query","required":false,"description":"Maximum number of events fetched per resolved series.","schema":{"type":"integer","minimum":1,"maximum":200,"default":12},"example":12},{"name":"startFrom","in":"query","required":false,"description":"Only events starting at or after this instant (ISO 8601 with a UTC offset). `startingEvents` are included only in the slice that covers the current time, and `matchingMarkets` only holds entries for the events returned.","schema":{"type":"string","format":"date-time"},"example":"2026-09-25T04:00:00Z"},{"name":"startTo","in":"query","required":false,"description":"Only events starting before this instant (ISO 8601 with a UTC offset). Must be after `startFrom`.","schema":{"type":"string","format":"date-time"},"example":"2026-09-28T04:00:00Z"}],"responses":{"200":{"description":"Upcoming events within the window, plus matched cross-provider markets. Returns `{\"events\": [], \"startingEvents\": [], \"matchingMarkets\": {}}` on a genuine no-data result.","headers":{"Cache-Control":{"schema":{"type":"string","example":"public, max-age=30, s-maxage=60"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SportsUpcomingEventsResponse"}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"502":{"description":"All upstream event-source fetches failed while building a cold cache entry.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"All upstream fetches failed"}}}}}}}}},"/sports/futures":{"get":{"operationId":"getSportsFutures","summary":"Season/tournament futures markets across sports","description":"Returns futures-style (non-per-game) sports markets for providers Polymarket and Predict.fun (open/active status only). Per-match/per-game rows are excluded; each event is classified into a (category, league) pair from a large token/phrase table, falling back to an \"other\" bucket, and titles that look like props/game lines/drafts or, for the \"other\" bucket only, non-sport mis-tags are dropped. Each event is capped at 30 outcomes (highest-priced first, unpriced sink to the bottom); the \"other\" bucket is additionally capped at 150 events (richest-first); recognized-sport events are uncapped. Results are sorted by market count descending. Responses are served from a cache kept warm in the background. With no parameters the whole list is returned; `mode`, `category`, `providers` and `q` filter it, and `limit` switches to pages carrying `total` and `nextOffset`.","tags":["Sports"],"x-kairos-auth":"public","security":[],"x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","parameters":[{"name":"mode","in":"query","required":false,"description":"`sports` leaves out esports and the \"other\" bucket unless `category` names one; `esports` keeps only esports futures and filters `category` by title.","schema":{"type":"string","enum":["sports","esports"]}},{"name":"category","in":"query","required":false,"description":"`all`, a catalog category id (e.g. `basketball`, `other`), or with `mode=esports` an esports title slug or `__esports_other` for titles outside the catalog.","schema":{"type":"string","maxLength":64,"default":"all"}},{"name":"providers","in":"query","required":false,"description":"Comma-separated provider names to keep (e.g. `polymarket,predictfun`).","schema":{"type":"string","maxLength":200}},{"name":"q","in":"query","required":false,"description":"Space-separated terms that must all appear in the event title or a contender label.","schema":{"type":"string","maxLength":200}},{"name":"offset","in":"query","required":false,"description":"Position in the filtered list to start from; use the previous page's `nextOffset`.","schema":{"type":"integer","minimum":0,"default":0}},{"name":"limit","in":"query","required":false,"description":"Page size. When set, the response carries `total` and `nextOffset`.","schema":{"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"Futures/outright markets grouped by event, each with its priced outcomes.","headers":{"Cache-Control":{"schema":{"type":"string","example":"public, max-age=60, s-maxage=300"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SportsFuturesResponse"}}}},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/sports/tournament-bracket":{"get":{"operationId":"getSportsTournamentBracket","summary":"Tournament bracket with live cross-provider prices per tie","description":"Builds a tournament bracket for `league`. Bracket structure and results are sourced from third-party sports-data providers depending on the league. Each tie/match is enriched with live prices from Polymarket, Kalshi (authenticated API), and Predict.fun where a corresponding market can be matched. `format=symmetric` (default) returns a left/right bracket converging on `final`, with an optional `thirdPlace` match and, for `format=groups-then-knockout`, a `groups` stage with standings tables. `format=left-to-right` instead returns all rounds in `leftRounds` with an empty `rightRounds`. `winner` is always null — this endpoint never resolves a champion. Responses are briefly cached.","tags":["Sports"],"x-kairos-auth":"public","security":[],"x-kairos-rate-limit":"10/minute","x-kairos-bucket":"heavy","parameters":[{"name":"league","in":"query","required":true,"description":"League slug, e.g. \"ucl\" (UEFA Champions League). Must resolve via a known league table.","schema":{"type":"string"},"example":"ucl"},{"name":"format","in":"query","required":false,"description":"Bracket layout. \"symmetric\" converges left/right rounds on a final; \"left-to-right\" is a single linear round list; \"groups-then-knockout\" adds a group stage.","schema":{"type":"string","enum":["symmetric","left-to-right","groups-then-knockout"],"default":"symmetric"},"example":"symmetric"}],"responses":{"200":{"description":"Tournament bracket structure with live provider prices attached per tie.","headers":{"Cache-Control":{"schema":{"type":"string","example":"public, max-age=60, stale-while-revalidate=3600"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SportsTournamentBracketResponse"}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/sports/game-markets":{"get":{"operationId":"getSportsGameMarkets","summary":"Cross-venue market families for a single game, grouped by section","description":"Returns one game's markets grouped into sections such as game lines, halves, player props, exact score, and corners. Cross-venue families can include Polymarket, Kalshi, Predict.fun, and Hyperliquid. Each family is either a pill of named outcomes or a ladder of ordered Over/Under rungs. Tradeable legs carry provider identifiers and pricing; equivalent families and outcomes share opaque `mergeKey` and `outcomeKey` values. Combo-capable legs include their eligibility and position identifiers. Responses are briefly cached.","tags":["Sports"],"x-kairos-auth":"public","security":[],"x-kairos-rate-limit":"60/minute","x-kairos-bucket":"sports","parameters":[{"name":"slug","in":"query","required":false,"description":"Game/event slug. Supply exactly one of `slug` or `game_id`.","schema":{"type":"string","minLength":1,"maxLength":200},"example":"nba-lal-bos-2026-01-15"},{"name":"game_id","in":"query","required":false,"description":"Provider game id. Supply exactly one of `game_id` or `slug`; this is preferred when the discovery response carries one.","schema":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[A-Za-z0-9_-]+$"},"example":"12345678"}],"responses":{"200":{"description":"Cross-venue markets grouped into sections of pill/ladder market families.","headers":{"Cache-Control":{"schema":{"type":"string","example":"public, max-age=30"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SportsGameMarketsResponse"}}}},"400":{"description":"Neither identifier was supplied, or both were supplied.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","example":"exactly one of game_id or slug is required"}}}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/sports/combo-markets":{"get":{"operationId":"getSportsComboMarkets","summary":"Catalog of combo-eligible Polymarket markets","description":"Returns the catalog of markets eligible for parlay/combo construction, filtered to Polymarket only (combos are Polymarket-only). Responses are briefly cached, with a self-healing guard that discards and rebuilds a cache entry left over from a pre-migration response shape.","tags":["Sports"],"x-kairos-auth":"public","security":[],"x-kairos-rate-limit":"200/minute","x-kairos-bucket":"market_data","responses":{"200":{"description":"Combo-eligible market catalog.","headers":{"Cache-Control":{"schema":{"type":"string","example":"public, max-age=300"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SportsComboMarketsResponse"}}}},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/sports/metadata":{"get":{"operationId":"getSportsMetadata","summary":"Synced sports teams and leagues reference data","description":"Returns every synced team and league for frontend lookups (logos, abbreviations, aliases, colors), ordered by name/sport. Served from a shared server cache refreshed every 15 minutes and never more than 24 hours old; the `Age` header gives the seconds since the body was built.","tags":["Sports"],"x-kairos-auth":"public","security":[],"x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","responses":{"200":{"description":"All synced teams and leagues.","headers":{"Cache-Control":{"schema":{"type":"string","example":"public, max-age=3600, s-maxage=3600"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SportsMetadataResponse"}}}},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/matched-markets/enriched":{"get":{"operationId":"getEnrichedMatchedMarkets","summary":"Matched markets with identifiers, outcomes, and current reference prices","description":"Returns the same verified, live-filtered pair catalog as `/matched-markets`, with `details` and `pricing` attached to both sides. Unsupported markets, missing quotes, and individual venue failures produce `pricing: null` without changing pair membership or pagination. Prices use each venue's API scale and are reference prices, not executable bids or asks. The page is capped at 150 pairs to bound request cost. Responses are not cacheable.","tags":["Sports"],"x-kairos-auth":"public","security":[],"x-kairos-rate-limit":"60/minute","x-kairos-bucket":"sports","parameters":[{"name":"limit","in":"query","required":false,"description":"Maximum number of enriched pairs to return.","schema":{"type":"integer","minimum":1,"maximum":150,"default":50},"example":50},{"name":"offset","in":"query","required":false,"description":"Legacy pagination offset. Do not combine with cursor.","schema":{"type":"integer","minimum":0,"default":0},"example":0},{"name":"cursor","in":"query","required":false,"description":"Opaque keyset cursor. Send an empty value on the first page, then pass each response next_cursor. A 409 requires restarting the walk.","schema":{"type":"string","maxLength":16384},"example":""},{"name":"provider","in":"query","required":false,"description":"Filter to pairs where either side belongs to this provider.","schema":{"type":"string"},"example":"polymarket"},{"name":"min_similarity","in":"query","required":false,"description":"Minimum similarity, clamped server-side to at least 0.82.","schema":{"type":"number","format":"double","minimum":0,"maximum":1,"default":0.82},"example":0.85},{"name":"sort_by","in":"query","required":false,"description":"Offset-mode sort column. Cursor mode orders by canonical pair identity.","schema":{"type":"string","enum":["similarity","updated_at"],"default":"similarity"}},{"name":"include_total","in":"query","required":false,"description":"Include the total matching-pair count under pre-live filters.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"A page of verified pairs with details and best-effort reference pricing.","headers":{"Cache-Control":{"schema":{"type":"string","example":"no-store"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SportsEnrichedMatchedMarketsResponse"}}}},"400":{"description":"Unknown provider, invalid cursor/sort, or cursor combined with a non-zero offset.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}}}}},"409":{"description":"The catalog changed during a cursor walk; restart with an empty cursor.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"503":{"description":"Matched-market or enrichment data could not be loaded.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}}}}}}}},"/matched-markets":{"get":{"operationId":"getMatchedMarkets","summary":"Paginated catalog of verified cross-venue matched markets","description":"Returns embedding-verified cross-venue market matches (verified matches only), joined against market metadata for display (title, ticker, images, expiry, status). `min_similarity` is clamped server-side to never go below a minimum threshold regardless of what the caller passes. Pairs are dropped if either correlation side has expired, if either side references a provider that is unknown or not currently indexing-enabled, or if either side's resolved market status is settled/closed/resolved; a side with no metadata available (unmirrored rather than de-indexed) is kept with null display fields. `has_more` reflects whether the underlying page (before the live-status post-filter) was full, so it is not a strict function of the returned `count`/`pairs` length. `total` is only computed (filters applied before the live-status post-filter) when `include_total=true`. This is treated as a primary, non-additive endpoint: any backend failure returns 503 rather than a degraded/empty result.","tags":["Sports"],"x-kairos-auth":"public","security":[],"x-kairos-rate-limit":"60/minute","x-kairos-bucket":"sports","parameters":[{"name":"limit","in":"query","required":false,"description":"Maximum number of pairs to return.","schema":{"type":"integer","minimum":1,"maximum":1000,"default":50},"example":50},{"name":"offset","in":"query","required":false,"description":"Legacy pagination offset. Do not combine with cursor.","schema":{"type":"integer","minimum":0,"default":0},"example":0},{"name":"cursor","in":"query","required":false,"description":"Opaque keyset cursor. Send an empty value on the first page, then pass each response next_cursor. Cursor mode orders by immutable canonical pair identity so catalogue mutations cannot shift page boundaries, and binds every page to one full-set fingerprint.","schema":{"type":"string","maxLength":16384},"example":""},{"name":"provider","in":"query","required":false,"description":"Filter to pairs where either side belongs to this provider. Must resolve to a known provider name (case-insensitive) or the request fails with 400.","schema":{"type":"string"},"example":"polymarket"},{"name":"min_similarity","in":"query","required":false,"description":"Minimum embedding similarity score. Always clamped up server-side to a minimum threshold even if a lower value is supplied.","schema":{"type":"number","format":"double","minimum":0,"maximum":1,"default":0.82},"example":0.85},{"name":"sort_by","in":"query","required":false,"description":"Sort column. Must be \"similarity\" or \"updated_at\"; any other value fails with 400.","schema":{"type":"string","enum":["similarity","updated_at"],"default":"similarity"},"example":"similarity"},{"name":"include_total","in":"query","required":false,"description":"When true, runs an additional COUNT query and includes the total matching-pair count in the response.","schema":{"type":"boolean","default":false},"example":false}],"responses":{"200":{"description":"A page of verified cross-venue matched market pairs.","headers":{"Cache-Control":{"schema":{"type":"string","example":"public, max-age=10"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SportsMatchedMarketsResponse"}}}},"400":{"description":"Unknown provider name, or sort_by is not one of the supported columns.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Unknown provider 'foo'"}}}}}},"409":{"description":"The correlation catalogue changed during a cursor walk. Discard accumulated pages and restart with an empty cursor.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"503":{"description":"The correlation store could not be queried.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","example":"Correlation store unavailable"}}}}}}}}},"/arb-bets/":{"get":{"operationId":"getArbOpportunities","summary":"Cross-venue arbitrage opportunities from the Arb Bets feed","description":"Server-side proxy to the Arb Bets vendor feed, called with a Kairos-held key so the credential never reaches a client. Fixed vendor parameters: $100 investment, 1.0% minimum profit. The body is the vendor's payload passed through unchanged — Kairos does not normalize it, and its shape is owned by the vendor. Note the **trailing slash**: the route is registered at `/arb-bets/`, and `/arb-bets` redirects.\n","tags":["Markets"],"x-kairos-auth":"session","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","responses":{"200":{"description":"Vendor payload, passed through.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArbBetsResponse"}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"No Arb Bets key is configured on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"},"example":{"detail":"Arb Bets API key not configured"}}}},"503":{"description":"Kairos' vendor credential was rejected upstream.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"},"example":{"detail":"Arb Bets is down right now. Please check back later."}}}},"default":{"description":"Any other upstream failure is passed through with the vendor's status code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"},"example":{"detail":"Arb Bets API error: 502"}}}}}}},"/market-links/featured":{"get":{"operationId":"getFeaturedMarketLinks","summary":"Ranked cross-venue links with a union book","description":"The cross-venue \"matches\" catalog: every approved global market link (one real-world contract listed on 2–4 venues) for which order execution asserts a union order book per side. Rows are ranked by the largest Discover hourly-volume score across their legs and paged by an opaque `cursor`. Served straight from the catalog cron's Redis keys, rebuilt every 60 s; empty until the first cycle after a deploy. Each leg's `executable` mirrors what `GET /orders/route-fees` on the execution API reports for that venue, so a row says which venues an order can be routed to and which are display-only.\n\nCached with `Cache-Control: public, max-age=15, s-maxage=30`.\n","tags":["Markets"],"x-kairos-auth":"public","security":[],"x-kairos-rate-limit":"200/minute","x-kairos-bucket":"market_data","parameters":[{"name":"limit","in":"query","description":"Rows per page.","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"cursor","in":"query","description":"The previous page's `nextCursor`; omit for the first page.","schema":{"type":"string","maxLength":16}}],"responses":{"200":{"description":"One page of links in rank order.","headers":{"Cache-Control":{"schema":{"type":"string","example":"public, max-age=15, s-maxage=30"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketLinksFeaturedResponse"}}}},"400":{"description":"A cursor this endpoint did not issue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"503":{"description":"The catalog store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/market-links/lookup":{"get":{"operationId":"lookupMarketLinks","summary":"The cross-venue link holding each venue market","description":"Batched reverse lookup from venue markets to the catalog row that holds them. A market is matched by its executor market id, its stream key (the numeric Gamma id for Polymarket), or — for a Kalshi two-ticker game — the ticker that books the link's NO side. References without a published link are omitted from the response rather than returned empty.\n\nCached with `Cache-Control: public, max-age=15, s-maxage=30`.\n","tags":["Markets"],"x-kairos-auth":"public","security":[],"x-kairos-rate-limit":"200/minute","x-kairos-bucket":"market_data","parameters":[{"name":"markets","in":"query","required":true,"description":"Comma-separated `<provider>:<marketId>` references, max 100 distinct. Only the first colon separates the two parts, so market ids may contain colons; the provider is case-insensitive.","schema":{"type":"string","minLength":1,"maxLength":8192},"example":"polymarket:0x8a9f8be5bef9bc8d36c7895707a4f2561d49de9f5467b6675ba206196e8bd2e2,kalshi:KXNFLGAME-26OCT01PITCLE-CLE"}],"responses":{"200":{"description":"The link for each referenced market that has one, keyed by the reference as given (provider lower-cased).","headers":{"Cache-Control":{"schema":{"type":"string","example":"public, max-age=15, s-maxage=30"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketLinksLookupResponse"}}}},"400":{"description":"No parseable `<provider>:<marketId>` reference, or more than 100 of them.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"503":{"description":"The catalog store could not be read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}}}}},"/market-clusters":{"get":{"operationId":"getMarketClusters","summary":"Cross-venue cluster membership for a set of markets","description":"Resolves each `<provider_id>:<market_id>` reference to its equivalence cluster at the requested tier floor and returns the cluster's other live members. Members listed on a venue whose indexing is switched off, or whose status is settled/closed/resolved, are excluded; a reference whose cluster keeps fewer than two live members after that filter is omitted from the response entirely. Membership is capped at 32 per cluster, with `truncated` and the stored `size` stating what was left out.\n","tags":["Markets"],"x-kairos-auth":"public","security":[],"x-kairos-rate-limit":"60/minute","x-kairos-bucket":"sports","parameters":[{"name":"markets","in":"query","required":true,"description":"Comma-separated `<provider_id>:<market_id>` references, max 200. Only the first colon separates the two parts, so market ids may themselves contain colons. Unparseable and duplicate references are dropped silently.","schema":{"type":"string"},"example":"1:0x1234abcd,2:KXNBA-26JAN15-LAL"},{"name":"floor","in":"query","description":"Tier floor to resolve membership at.","schema":{"type":"string","enum":["exact","semantic"],"default":"exact"}}],"responses":{"200":{"description":"Cluster membership keyed by the requested reference.","headers":{"Cache-Control":{"schema":{"type":"string","example":"public, max-age=10"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketClustersResponse"}}}},"400":{"description":"Unknown `floor`, no parseable `<provider_id>:<market_id>` reference, or more than 200 references.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"503":{"description":"The cluster store could not be queried.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"},"example":{"detail":"Cluster store unavailable"}}}}}}},"/txodds/fixtures":{"get":{"operationId":"listTxoddsFixtures","summary":"List sports fixtures with live status and win probabilities","description":"Fixture board backed by the TxODDS score event store. Kick-off bounds are snapped to the minute — `from_ms` rounds down and `to_ms` rounds up, so the window may extend up to 59s past what was asked for. Per-row status is computed at cache time from feed phase plus wall clock, so a live-status flip can lag by up to one cache TTL. Concurrent misses on the same page share a single build.\n","tags":["Sports"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","parameters":[{"name":"competition_id","in":"query","description":"Restrict to one competition.","schema":{"type":["integer","null"]}},{"name":"sport","in":"query","schema":{"type":["string","null"],"enum":["soccer","usfootball"]}},{"name":"from_ms","in":"query","description":"Kick-off lower bound, epoch milliseconds. Rounded down to the minute.","schema":{"type":["integer","null"]}},{"name":"to_ms","in":"query","description":"Kick-off upper bound, epoch milliseconds. Rounded up to the minute. When both bounds are given the span may not exceed 400 days.","schema":{"type":["integer","null"]}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"offset","in":"query","description":"Bounded so deep paging cannot mint unlimited cache keys.","schema":{"type":"integer","minimum":0,"maximum":5000,"default":0}}],"responses":{"200":{"description":"One page of fixtures.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TxoddsFixtureList"}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"422":{"description":"Parameter validation failed — unknown `sport`, out-of-range `limit`/`offset`, `from_ms` greater than `to_ms`, or a window wider than 400 days. The inverted/oversized-window cases return the plain `{\"detail\": \"...\"}` envelope, not the field-list envelope.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"},"example":{"detail":"from_ms must not exceed to_ms"}}}},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/txodds/fixtures/{fixture_id}":{"get":{"operationId":"getTxoddsFixture","summary":"Score, timeline, and possession metrics for one fixture","description":"Full detail for a single fixture: scoreline, event timeline, and possession-derived metrics. Independent parts are read concurrently and degrade individually — a part that fails is returned in its empty shape rather than failing the response, and the degraded payload is cached for a shorter time than a clean one. Possession metrics are soccer-only; `usfootball` fixtures carry their situational state in `football` instead.\n","tags":["Sports"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","parameters":[{"name":"fixture_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"exclusiveMaximum":9223372036854776000},"example":4821993}],"responses":{"200":{"description":"Fixture detail, possibly degraded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TxoddsFixtureDetail"}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"404":{"description":"No such fixture. Misses are cached, so a repeated probe for an unknown id does not reach the store.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"},"example":{"detail":"fixture 4821993 not found"}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"}}}},"/txodds/fixtures/{fixture_id}/timing":{"get":{"operationId":"getTxoddsFixtureTiming","summary":"Source-observed soccer and NFL fixture timing","description":"Fresh source clock evidence for one TxODDS fixture. No response caching or client countdown extrapolation. Missing, legacy, stale, interrupted, unsupported, or malformed evidence returns usableForLateGame=false with an explicit reason. Requires a verified fixture-to-market mapping before use by a trading strategy.","tags":["Sports"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","parameters":[{"name":"fixture_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"exclusiveMaximum":9223372036854776000}}],"responses":{"200":{"description":"Timing evidence, including explicit unavailability.","headers":{"Cache-Control":{"schema":{"type":"string"},"description":"no-store"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TxoddsFixtureTiming"}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"404":{"description":"Fixture not found."},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"},"500":{"description":"Timing source unavailable; no eligible fallback is returned."},"502":{"description":"Invalid source observation."}}}},"/txodds/fixtures/{fixture_id}/timeseries":{"get":{"operationId":"getTxoddsFixtureTimeseries","summary":"Win-probability history and market read for one fixture","description":"The odds-derived curves for a fixture, split out from the detail because they read a table orders of magnitude larger than the score store. Both parts degrade independently: a failed win-probability read returns an empty history, a failed market read returns null projections, and a degraded payload gets a shorter cache TTL. Otherwise the TTL follows kick-off and the last sample.\n","tags":["Sports"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"100/minute","x-kairos-bucket":"default","parameters":[{"name":"fixture_id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"exclusiveMaximum":9223372036854776000},"example":4821993}],"responses":{"200":{"description":"Win-probability samples and the current market read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TxoddsFixtureTimeseries"}}}},"401":{"$ref":"#/components/responses/DataUnauthorized"},"403":{"$ref":"#/components/responses/DataForbidden"},"404":{"description":"No such fixture.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DataError"}}}},"422":{"$ref":"#/components/responses/DataValidationError"},"429":{"$ref":"#/components/responses/DataRateLimited"}}}}}}},{"id":"execution","title":"Kairos Order Execution API","baseUrls":["https://execution.kairos.trade","https://eu-west-1-polymarket.executor.kairos.trade","https://ap-northeast-1-predictfun.executor.kairos.trade","https://staging-execution.kairos.trade"],"spec":{"openapi":"3.1.0","info":{"title":"Kairos Order Execution API","version":"1.0.0","summary":"Order entry, cancellation, fee quotes, and live position exposure across venues.","description":"The Order Execution API at `execution.kairos.trade` places and manages\norders across every venue Kairos integrates. Two lanes:\n\n- **Custodial** (`POST /orders`) — Kairos signs and routes on your behalf\n  (custodial signing). Async ack: a 200 means validated + enqueued; track via\n  `GET /orders/{order_id}` or the WebSocket order/fill stream.\n- **Self-custody / external signing** (`POST /v2/orders/intent` +\n  `POST /v2/orders/submit`) — for allow-listed institutional accounts on\n  Polymarket and Predict.fun: Kairos builds the EIP-712 payload, you sign\n  with your own key, submission is synchronous with the venue's verbatim\n  result. `POST /v2/onchain/intent` + `POST /v2/onchain/submit` are the\n  same pattern for on-chain operations (approvals, redeem, split/merge,\n  unwrap) on Polygon, where you also pay the gas.\n\n## Authentication\n\nAll endpoints require a Kairos API key (`X-Client-Id` + `X-Api-Key` +\n`X-Api-Secret`) or a first-party session JWT, plus the per-operation\nscope (`trade:execute`, `trade:read`, `position:read`) where the operation\ndocuments one. Synthetic Books accepts any otherwise-valid account\ncredential without an extra scope or allowlist. There is\nno anonymous tier. Scope checks apply to API-key credentials only; a\nsession JWT carries no scope list and is treated as holding every scope.\n\nThe custodial mutation endpoints (`POST /orders`, the three cancel\nendpoints) and Synthetic Books create/refresh/release carry an additional\n`RequireServiceToken` gate satisfied by **any** of: the full API-key triple,\nan internal `X-Service-Token`, or a valid `X-Csrf-Token`. API-key consumers\nneed no extra header — but a bare `Authorization: Bearer <jwt>` alone is NOT\nsufficient on those routes. The `/v2/orders/*` and `/v2/onchain/*`\nexternal-signing routes have no such gate: a session JWT alone works there.\n\n## Rate limits\n\nOrder submission is capped per user (default 5 orders per **second**,\nsliding window, `ORDER_RATE_LIMIT_PER_SEC`); some credentials carry an\n`orders` override, which is enforced on its own per-credential window of\nthe same duration *in addition to* the per-user window — either one\ndenying is a `429`. Idempotent replays (same `client_order_id`) return the\nexisting order before the limiter runs and never consume a slot. The\nlimiter is Redis-backed and **fails closed**: if Redis is unreachable the\nsubmission is denied with `429`.\n\nSeparately, repeated *authentication failures* from one client IP are\nthrottled at 10 failures per 60 s (also fail-closed). That limiter returns\nthe minimal `{\"error\": \"Too many authentication attempts\"}` body.\n\n**No rate-limit headers.** This service does not emit `Retry-After`,\n`X-RateLimit-*`, or any other backoff hint on a `429` — back off on your\nown schedule.\n\n## Error responses\n\n**Six different error body shapes are in use across this service and they\nare not interchangeable.** Check the shape documented on the specific\noperation before writing a parser; a client that assumes one shape will\nread `undefined` for the reason on the others.\n\n1. `OrderErrorResponse` — the structured envelope (`error`, `code`,\n   `error_details{code,message,details?,metadata?,actions}`) returned by\n   every handler that surfaces an `ExecutionError`/`ApiError`: order\n   submission, cancel-all, the CTF endpoints, the Polymarket/Opinion\n   onboarding endpoints, Hyperliquid withdraw/transfer, and most of the\n   deposit-wallet family. Note the two `code` fields differ in case:\n   top-level `code` is PascalCase (`\"AuthInsufficientScope\"`), while\n   `error_details.code` is the SCREAMING_SNAKE_CASE wire code\n   (`\"AUTH_INSUFFICIENT_SCOPE\"`). Match on `error_details.code`.\n2. `OrderSimpleErrorResponse` — the minimal `{\"error\": \"...\"}` (sometimes\n   with `code`) used by the auth middleware (any endpoint's\n   `401`/`403`/`429`), the whole `/v2/*` external-signing and on-chain\n   lane, the Kalshi and Predict.fun endpoints.\n3. `OrderMarketLinkErrorResponse` — `{\"message\": \"...\"}`, with no `error`\n   and no `code`. Used by `POST /orders/market-links` **only**.\n4. **Empty or plain-text, not JSON at all.** Several deposit-wallet\n   endpoints — most importantly\n   the RPC signed-batch submission and\n   `POST /exchanges/polymarket/imported/relay-info` — return most 4xx/5xx\n   responses with a **completely empty body** and `content-type:\n   text/plain`. A handful of cases carry a bare plain-text sentence\n   (`batch is not an allowed withdrawal or collateral conversion`,\n   `relayer rejected batch: …`). Do not attempt to JSON-parse these.\n5. Handlers whose Rust signature returns a bare `StatusCode` likewise send\n   **no body at all**; those responses are marked \"empty body (status code\n   only)\".\n6. `SyntheticBookErrorResponse` — `{\"error\": \"stable_snake_case_code\",\n   \"message\": \"...\", \"details\"?: {...}}` on `/v1/synthetics*`. Match the\n   top-level `error`; upstream definition validation may be nested under\n   `details`.\n\nA further wrinkle inside shape 1: some provider-access checks discard the\nspecific reason and return a generic `\"Request failed with status 403\"` /\n`AUTH_CREDENTIALS_INVALID` body, while others preserve\n`AUTH_INSUFFICIENT_SCOPE` and the real message. Do not rely on the message\ntext of an access denial being stable.\n\nThe central `ExecutionError` → HTTP mapping (`ApiError::from`) is:\n\n| `ExecutionError` | Status | `error_details.code` |\n|---|---|---|\n| `InsufficientBalance` | 400 | `FUNDS_INSUFFICIENT_USDC` |\n| `CollateralLocation` | 400 | `FUNDS_COLLATERAL_LOCATION` |\n| `InvalidOrder` | 400 | `VALIDATION_INVALID_ORDER` |\n| `PositionShortfall` | 400 | `FUNDS_INSUFFICIENT_BALANCE` |\n| `MarketClosed` | 400 | `EXCHANGE_POLYMARKET_MARKET_CLOSED` |\n| `OrderAlreadyCancelled` | 400 | `VALIDATION_INVALID_ORDER` |\n| `UnsupportedExchange` | 400 | `EXCHANGE_UNSUPPORTED` |\n| `SlippageExceeded` | 400 | `MARKET_FOK_NOT_FILLED` |\n| `FokNotFilled` | 400 | `MARKET_FOK_NOT_FILLED` |\n| `AuthenticationError` | 401 | `AUTH_CREDENTIALS_INVALID` |\n| `CredentialError` | 401 | `AUTH_CREDENTIALS_NOT_FOUND` |\n| `MarketNotFound` | 404 | `VALIDATION_MARKET_NOT_FOUND` |\n| `OrderNotFound` | 404 | `VALIDATION_INVALID_ORDER` |\n| `MarketNotSettledOnChain` | 409 | `VALIDATION_MARKET_NOT_SETTLED_ON_CHAIN` |\n| `OrderbookUnavailable` | 422 | `ORDERBOOK_UNAVAILABLE` |\n| `ExchangeError` (code `429`/`RATE_LIMITED`) | 429 | `EXCHANGE_POLYMARKET_RATE_LIMITED` |\n| `ExchangeError` (code `401`/`UNAUTHORIZED`) | 401 | `AUTH_POLYMARKET_API_KEY_INVALID` |\n| `SigningError` | 500 | `SIGNATURE_ERROR` |\n| `DatabaseError` | 500 | `DATABASE_ERROR` |\n| `InternalError` / `LedgerReconciliationRequired` / `SponsoredRequestWedged` / `PreTradeError` | 500 | `INTERNAL_ERROR` |\n| `NetworkError`, `ExchangeError` (any other code) | 502 | `NETWORK_ERROR` / venue-classified |\n| `LockError` | 503 | `INTERNAL_ERROR` |\n| `Timeout` | 504 | `NETWORK_TIMEOUT` |\n\nA venue `ExchangeError` is further classified from the venue's own message\ntext before it is mapped, so an \"allowance is not enough\" rejection becomes\n`ALLOWANCE_CTF_NOT_SET`, a \"not enough balance\" rejection becomes\n`FUNDS_INSUFFICIENT_BALANCE`, \"post-only mode\" becomes `MARKET_NOT_READY`,\na \"no liquidity\" rejection becomes `MARKET_INSUFFICIENT_LIQUIDITY`, and so\non. Match on `error_details.code`, never on `error`.\n\n## Server-level guards\n\nEvery request is subject to a 120 s timeout (`OE_REQUEST_TIMEOUT_SECS`), a\n2 MiB request-body cap (`OE_MAX_BODY_BYTES`, over-size bodies get `413`),\nand a 1024-request global concurrency ceiling\n(`OE_MAX_CONCURRENT_REQUESTS`). Every response carries `X-Content-Type-Options:\nnosniff`, `X-Frame-Options: DENY`, HSTS, and a `default-src 'none'` CSP.\n\n## Conventions\n\n- Prices are decimal strings on the 0–1 scale; `*_bps` fields are the\n  same value in basis points (× 10000).\n- Quantities/sizes are decimal strings.\n- Structured errors carry `error_details.code`\n  (SCREAMING_SNAKE_CASE) plus actionable recovery `actions`.\n\n## Changelog\n\n**2026-09-14 — collateral routing fields on `POST /orders`.** Four optional\nrequest fields and one optional response field. `collateral`\n(`skip` | `check` | `fund`) **defaults to** `skip`, which is exactly today's\npath: no affordability check, no hold, no funding, zero added latency — so\nno existing caller changes behaviour without opting in. `shard_funding`\n**defaults to** `true`, which is also exactly today's behaviour: the Kalshi\nshard move is same-venue, zero-fee and already runs for every account; send\n`false` to opt out. `collateral=fund` requires both `max_bridge_fee_usdc`\nand `max_funding_wait_ms` and is rejected `400` naming the missing cap;\neither cap without `fund` is likewise a `400`. The response gains\n`funding`, `null` whenever no funding work ran. FIX sessions carry the same\nfields on `NewOrderSingle(D)` as optional tags `5701`–`5704`; a session that\nsends none of them is unchanged.\n","contact":{"name":"Kairos","url":"https://app.kairos.trade/docs/api-reference"},"termsOfService":"https://kairos.trade/terms"},"servers":[{"url":"https://execution.kairos.trade","description":"Production (central primary, us-east-1)"},{"url":"https://eu-west-1-polymarket.executor.kairos.trade","description":"Production regional execution node — Ireland, colocated with Polymarket. Same API surface; assigned at onboarding."},{"url":"https://ap-northeast-1-predictfun.executor.kairos.trade","description":"Production regional execution node — Tokyo, colocated with Predict.fun. Same API surface; assigned at onboarding."},{"url":"https://staging-execution.kairos.trade","description":"Staging"}],"security":[{"apiKeyClientId":[],"apiKeyKey":[],"apiKeySecret":[]}],"components":{"securitySchemes":{"apiKeyClientId":{"type":"apiKey","in":"header","name":"X-Client-Id","description":"Credential client id (`kairos_ck_...`). Must be sent together with X-Api-Key and X-Api-Secret."},"apiKeyKey":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"64-char hex API key."},"apiKeySecret":{"type":"apiKey","in":"header","name":"X-Api-Secret","description":"64-char hex API secret."},"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"First-party Kairos session JWT (web app sessions). Not issued to API consumers."}},"responses":{"ExecUnauthorized":{"description":"No valid credential presented — missing/invalid API-key headers or an invalid/expired session token.\n","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","example":"Unauthorized"}}}}}}},"schemas":{"OrderSide":{"type":"string","description":"Order/intent side. Lower-case on the wire.","enum":["buy","sell"],"example":"buy"},"OrderKind":{"type":"string","description":"Order type. `market` orders still require a `price` (the limit you'll cross to); `limit` orders rest at `price` until filled or cancelled.","enum":["market","limit"],"example":"limit"},"OrderTimeInForce":{"type":"string","description":"Time-in-force, always UPPER-CASE on the wire. `GTC`/`GTD` are resting (limit-style); `FOK`/`FAK`/`IOC` are immediate taker executions.","enum":["GTC","GTD","FOK","FAK","IOC"],"example":"GTC"},"OrderStatus":{"type":"string","description":"Lifecycle status. `partial` is the wire spelling of a partially-filled order (NOT\n`partially_filled`).\n\nThe internal states `queued`, `locked` and `orphaned` are **persisted as `pending`**, so\nan order read back from `GET /orders` or `GET /orders/{order_id}` reports `pending` for\nall three — they are listed here because they are part of the type and can appear on\nin-process/streamed values. `executing` is the exception: once a worker claims the order\nfor submission it is persisted as `executing`, and read-back endpoints report `executing`\nuntil the venue acknowledges (then `live`) or the attempt fails. The one place a caller\nsees `queued` directly is the `status` field of a fresh `POST /orders` response, which is\nthe literal string `\"queued\"`.","enum":["pending","queued","locked","executing","live","partial","filled","cancelled","expired","failed","orphaned"],"example":"live"},"Order":{"type":"object","description":"Exchange-agnostic order record.","required":["id","user_id","exchange_id","market_id","side","kind","quantity","time_in_force","filled_quantity","status","created_at","updated_at","gas_sponsored","collateral_mode","shard_funding","holding_wallet"],"properties":{"id":{"type":"string","format":"uuid","description":"Internal Kairos order id."},"user_id":{"type":"string","description":"Owning user's id."},"exchange_id":{"type":"string","description":"Venue identifier (e.g. `polymarket`, `kalshi`, `predictfun`, `hyperliquid`).","example":"polymarket"},"market_id":{"type":"string","description":"Market/contract identifier on the venue (condition id for Polymarket; numeric HIP-4 outcome id for Hyperliquid)."},"token_id":{"type":"string","nullable":true,"description":"Outcome-token id. Hyperliquid uses a side coin such as `#1010` or `#1011`."},"side":{"$ref":"#/components/schemas/OrderSide"},"kind":{"$ref":"#/components/schemas/OrderKind"},"quantity":{"type":"string","description":"Order quantity (decimal string, full precision), shares/contracts.","example":"100"},"price":{"type":"string","nullable":true,"description":"Limit price as a decimal string in `[tick, 1]` (prediction-market venues).","example":"0.52"},"price_bps":{"type":"integer","nullable":true,"description":"`price` expressed in basis points (price × 10000), for DB/analytics compatibility.","example":5200},"time_in_force":{"$ref":"#/components/schemas/OrderTimeInForce"},"post_only":{"type":"boolean","description":"Whether this order was submitted maker-only. A post-only order is one the venue was told to REJECT rather than let cross the spread and take liquidity. Always present; `false` for ordinary orders and for venues with no post-only concept.","default":false,"example":false},"expires_at":{"type":"string","format":"date-time","nullable":true,"description":"Expiration timestamp for GTD orders."},"filled_quantity":{"type":"string","description":"Cumulative filled quantity (decimal string).","example":"0"},"avg_fill_price":{"type":"string","nullable":true,"description":"Size-weighted average fill price (decimal string, 0..1), null until any fill lands."},"avg_fill_price_bps":{"type":"integer","nullable":true,"description":"`avg_fill_price` in basis points."},"exchange_order_id":{"type":"string","nullable":true,"description":"Venue-assigned order handle. Null until the order reaches the venue."},"status":{"$ref":"#/components/schemas/OrderStatus"},"terminal_fill_verification":{"allOf":[{"$ref":"#/components/schemas/TerminalFillVerification"}],"nullable":true,"description":"Durable venue-terminal fill barrier. While `pending`, callers must retain protection even if another status field appears terminal. `complete` carries the exact venue cumulative that was folded before terminal publication."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"locked_by":{"type":"string","nullable":true,"description":"Worker id currently holding the execution lock, if any."},"lock_expires_at":{"type":"string","format":"date-time","nullable":true},"error_message":{"type":"string","nullable":true,"description":"Failure detail when `status` is `failed`."},"failure":{"$ref":"#/components/schemas/OrderFailure"},"fee_amount":{"type":"string","nullable":true,"description":"Fee charged for this order, decimal string."},"fee_currency":{"type":"string","nullable":true},"client_order_id":{"type":"string","nullable":true,"description":"Caller-supplied idempotency key, if one was provided at submission."},"wallet_id":{"type":"string","nullable":true,"description":"FK to the user's wallet used for this order."},"outcome":{"type":"string","nullable":true,"description":"Human-readable outcome label (e.g. \"Yes\", \"No\", a team name)."},"outcome_id":{"type":"string","nullable":true,"description":"Outcome id — equals `token_id` on Polymarket."},"trigger_price":{"type":"string","nullable":true,"description":"Trigger price for stop-loss/take-profit style orders, decimal string."},"trigger_price_bps":{"type":"integer","nullable":true},"gas_sponsored":{"type":"boolean","description":"Whether Kairos sponsored gas for this order (always `false` on the self-custody external-signing lane)."},"collateral_mode":{"type":"string","enum":["skip","check","fund"],"description":"The collateral mode resolved for this order at submit. Always present; `skip` for an order that asked for nothing.","example":"skip"},"shard_funding":{"type":"boolean","description":"Whether the Kalshi shard move was permitted for this order. Always present; `true` unless the caller opted out.","example":true},"max_bridge_fee_usdc":{"type":"string","nullable":true,"description":"The order's bridge-fee ceiling in USDC, decimal string. Only ever set under `collateral_mode=fund`."},"max_funding_wait_ms":{"type":"integer","nullable":true,"description":"The order's funding-wait ceiling in milliseconds. Only ever set under `collateral_mode=fund`."},"gas_amount":{"type":"string","nullable":true,"description":"Gas spent, decimal string, if applicable."},"maker_address":{"type":"string","nullable":true,"description":"On-chain maker/signer address, for Polymarket reconciliation."},"tx_hash":{"type":"string","nullable":true,"description":"On-chain transaction hash (Polygon), if the fill involved one."},"raw":{"nullable":true,"description":"Full raw venue request/response payload for audit. Present on `GET /orders/{order_id}`; stripped to `null` on `GET /orders` (list responses) to keep list payloads small."},"bot_id":{"type":"string","nullable":true,"description":"Trading-bot id that placed this order, if any (null for manual/API orders)."},"source":{"type":"string","nullable":true,"description":"Attribution for who initiated the order: `manual` (web UI), `bot`, `copytrade`, `api` (API-key auth), or `fastlane` (external-signing lane).","example":"api"},"metadata":{"nullable":true,"description":"Exchange-specific metadata (arbitrary JSON). Present on `GET /orders/{order_id}`; stripped to `null` on `GET /orders`."},"holding_wallet":{"type":"string","description":"On-chain wallet that holds (or will hold) the resulting shares — the EOA for legacy Polymarket orders, or the Safe deposit-wallet proxy for upgraded/external-signing users. Empty string for non-Polymarket orders."}}},"OrderSubmitRequest":{"type":"object","description":"Body for `POST /orders` (custodial order submission).","required":["exchange_id","market_id","side","kind","quantity"],"properties":{"user_id":{"type":"string","deprecated":true,"description":"Ignored. The authenticated caller's id is always used."},"exchange_id":{"type":"string","description":"Venue identifier, must be a registered exchange (e.g. `polymarket`, `kalshi`, `predictfun`, `hyperliquid`).","example":"polymarket"},"market_id":{"type":"string","description":"Market/contract identifier on the venue. Hyperliquid expects the numeric HIP-4 outcome id."},"token_id":{"type":"string","nullable":true,"description":"Outcome-token id. For Hyperliquid, pass the selected side coin `#<10 * market_id + side_index>`; it is required to select side 1."},"outcome":{"type":"string","nullable":true,"description":"Human-readable outcome label for display/attribution (e.g. `\"Yes\"`, `\"No\"`, a team name).","example":"Yes"},"side":{"type":"string","description":"`buy` or `sell`, case-insensitive on input.","enum":["buy","sell","BUY","SELL","Buy","Sell"],"example":"buy"},"kind":{"type":"string","description":"`market` or `limit`, lower-case exactly.","enum":["market","limit"],"example":"limit"},"quantity":{"type":"string","description":"Decimal string, shares/contracts. Must be > 0 and <= 1,000,000. A minimum order size (default `1.0`, `MIN_ORDER_QUANTITY`) applies to BUY orders only — SELL/close orders are allowed at any size so a position can be fully closed after partial fills. API-key-authenticated BUYs additionally require `quantity × price >= $5` notional; session-JWT callers and all SELLs are exempt.","example":"100"},"price":{"type":"string","description":"Decimal string, REQUIRED for every order (including market orders, where it is the limit you're willing to cross to). Must be > 0 and <= the venue's max price (typically `1.0` for prediction markets).","example":"0.52"},"time_in_force":{"type":"string","description":"`GTC`, `GTD`, `FOK`, `FAK`, or `IOC` (case-insensitive on input, normalized to upper-case). Defaults to `GTC` if omitted; an unrecognized non-empty value is rejected rather than silently downgraded to `GTC`. Must be a TIF the target venue's capabilities advertise.","default":"GTC","example":"GTC"},"post_only":{"type":"boolean","nullable":true,"description":"Maker-only. When `true` the venue must REJECT the order rather than let any part of it cross the spread and take liquidity — it is a guarantee, never a hint, so an order that cannot honour it is rejected instead of being downgraded to a taker. Requires `kind=limit` and `time_in_force` of `GTC` or `GTD`, on a venue whose capabilities advertise post-only support (currently Polymarket, Kalshi, Predict.fun and Hyperliquid — the last expresses maker-only as its `Alo` time-in-force rather than a flag, but the request field is the same). Any other combination is a 400. Defaults to `false`.","default":false,"example":false},"expiration_minutes":{"type":"integer","nullable":true,"description":"Minutes from now until expiry. Only meaningful (and validated) when `time_in_force=GTD`: must be in `[1, 43200]` (30 days).","example":60},"trigger_price":{"type":"string","nullable":true,"description":"Decimal string. Trigger price for stop-loss/take-profit style orders. Must be > 0 and, like `price`, <= the venue's max price."},"collateral":{"type":"string","nullable":true,"enum":["skip","check","fund"],"default":"skip","description":"How much of the collateral router runs for this order. `skip` (the server default) is today's path: no affordability check, no hold, no router call, zero added latency. `check` places an affordability check and a hold and refuses a known shortfall, but never moves money. `fund` is `check` plus cross-ledger funding inside the two caps below, and REQUIRES both of them. An unrecognised value is rejected `400` naming the three valid values; it is never downgraded to `skip`.","example":"skip"},"shard_funding":{"type":"boolean","nullable":true,"default":true,"description":"Kalshi only. Move collateral between the account's own Kalshi shards before submit. Defaults to `true` — same venue, zero fee, one REST call, and already every account's behaviour — so an existing caller is unaffected. Send `false` to skip it.","example":true},"max_bridge_fee_usdc":{"type":"string","nullable":true,"description":"Decimal string. The most bridge fee this order consents to pay, in USDC. Required when `collateral=fund` and rejected `400` otherwise; `0` is a valid cap meaning \"fund only if it is free\".","example":"1.50"},"max_funding_wait_ms":{"type":"integer","nullable":true,"description":"The longest this order consents to wait for funding, in milliseconds. Required when `collateral=fund` and rejected `400` otherwise.","example":30000},"gas_sponsored":{"type":"boolean","nullable":true,"description":"Requests gas sponsorship. Currently not implemented server-side."},"client_order_id":{"type":"string","nullable":true,"description":"Caller-supplied idempotency key. A retry with the same `(user, exchange_id, market_id, client_order_id)` returns the existing order instead of creating a duplicate, and does not consume a rate-limit slot. If omitted, a random id is generated server-side (so un-keyed retries are NOT deduped)."},"orderbook_levels":{"type":"array","deprecated":true,"description":"Ignored. The executor always fetches fresh orderbook data itself. Kept only for backwards-compatible request bodies.","items":{"type":"array","items":{"type":"string"}}},"max_slippage":{"type":"string","nullable":true,"deprecated":true,"description":"Deprecated — use `max_slippage_cents`. Decimal in `[0, 0.5]` (e.g. `0.10` = 10%)."},"max_slippage_cents":{"type":"integer","nullable":true,"description":"Maximum acceptable slippage in cents. Must be in `[1, 99]` if provided.","example":5},"max_retries":{"type":"integer","nullable":true,"description":"Maximum retry attempts for transient failures. Defaults to `0`."},"bot_id":{"type":"string","nullable":true,"description":"Trading-bot id this order is attributed to."},"source":{"type":"string","nullable":true,"description":"Order-source attribution override. Pass `copytrade` explicitly when relevant; otherwise the server derives it from the auth method (API-key auth → `api`, `bot_id` present → `bot`, else → `manual`).","example":"api"}}},"OrderSubmitResponse":{"type":"object","description":"Response for `POST /orders`.","required":["order_id","status"],"properties":{"order_id":{"type":"string","format":"uuid","description":"Internal Kairos order id. Use with `GET /orders/{order_id}` and the cancel endpoints."},"status":{"type":"string","description":"`queued` for a newly-created order; on an idempotent replay (matching `client_order_id`), the pre-existing order's current status (`pending`, `live`, `partial`, `filled`, `cancelled`, `expired`, or `failed`).","example":"queued"},"funding":{"nullable":true,"description":"What the collateral router did for this order. `null` whenever no funding work ran — every `skip` order, and every order while funding orchestration is still being built.","allOf":[{"$ref":"#/components/schemas/FundingOutcome"}]}}},"FundingOutcome":{"type":"object","description":"What the collateral router did for one order. Absent sub-fields mean that part did not happen.","required":["mode"],"properties":{"mode":{"type":"string","enum":["skip","check","fund"],"description":"The resolved collateral mode this outcome describes."},"hold_id":{"type":"string","format":"uuid","description":"The BUY hold the affordability check placed, when one was placed."},"intent_ref":{"type":"string","description":"The router intent that moved collateral, when one was opened."},"refused_reason":{"type":"string","description":"Why funding did not happen, for a mode that asked for it."}}},"OrderCancelResponse":{"type":"object","description":"Response for `POST /orders/{order_id}/cancel`. Returned with HTTP 200 even when `success` is `false`.","required":["success","order_id","venue_reconciled"],"properties":{"success":{"type":"boolean","description":"Whether cancellation and terminal fill verification are both complete. A venue DELETE acknowledgement alone still returns false while verification is pending."},"order_id":{"type":"string","format":"uuid"},"message":{"type":"string","description":"Human-readable outcome detail, e.g. \"Order cancelled on exchange\" or \"Order is being submitted to exchange. Please try again in a moment.\""},"filled_quantity":{"type":"string","nullable":true,"description":"Total cumulative filled quantity, decimal string, when terminal verification is complete. Omitted while pending or unknown; do not infer omitted as zero. The durable receipt repeats this exact cumulative in `final_filled_quantity`."},"avg_fill_price":{"type":"string","nullable":true,"description":"Average fill price (decimal, 0..1) of any portion that filled before the cancel landed. Omitted when nothing filled or unknown."},"venue_reconciled":{"type":"boolean","description":"True only when a venue-final snapshot was observed and its exact cumulative fill has passed through Kairos' position fold. Currently authoritative for Kalshi."},"reconciled_position":{"allOf":[{"$ref":"#/components/schemas/ReconciledPositionExposure"}],"nullable":true,"description":"Exact post-fold position authority. A reconciled order may omit this object, either because nothing filled or because the durable position projection could not be proven within the cancel budget; the cancel itself is still confirmed. Omission is never proof that the portfolio is flat."},"terminal_fill_verification":{"allOf":[{"$ref":"#/components/schemas/TerminalFillVerification"}],"nullable":true,"description":"Pending or completed durable receipt for the exact venue-order generation. A pending response is not a confirmed cancellation even when the venue acknowledged DELETE."}}},"OrderAmendRequest":{"type":"object","description":"Request body for `POST /orders/{order_id}/amend`. Reprice only — `quantity` exists solely to be refused.","properties":{"price":{"type":"number","description":"New limit price, strictly greater than 0 and less than 1, in the order's OWN outcome's terms (send a Kalshi `No` order's `No` price; do not complement it client-side). Omitted re-sends the price the order already has."},"quantity":{"type":"number","description":"Accepted only to be REFUSED with `409` `EXCHANGE_AMEND_QUANTITY_UNSUPPORTED`, before the order is even read. This route reprices; it does not resize. Refused rather than ignored so a caller never believes an order was resized when it was not."}}},"OrderAmendResponse":{"type":"object","description":"Response for `POST /orders/{order_id}/amend`. Returned with HTTP 200 even when `success` is `false`.","required":["success","order_id"],"properties":{"success":{"type":"boolean","description":"Whether the venue confirmed the amend. `false` is an AMBIGUOUS outcome, not an error status — the amend may have been refused, or it may have reached the venue and been applied without us learning so. That ambiguity is why it is not a 5xx, which would invite a blind retry against an order that may already carry the new price. A non-amendable status answers 200 with a false `success` too. On any `false`, re-read the order before acting on it."},"order_id":{"type":"string","format":"uuid","description":"The id that was addressed, always unchanged. An amend never mints a new order."},"exchange_order_id":{"type":"string","description":"The venue's order id, also unchanged — Kalshi AmendOrder v2 answers with the same id it was given, so there is no supersession to record. Omitted only on the pre-submission path, where the order has no venue identity yet."},"price":{"type":"string","description":"Decimal string. The new resting price on success. On an unsuccessful path it is the price we last recorded — the resting price when the venue explicitly refused, but only our last known value when the call failed in transit or came back unreadable, where the venue may in fact hold the new price. Omitted when unknown."},"quantity":{"type":"string","description":"Decimal string, the order's total size — always the size it was placed for. This route never resizes. Omitted when unknown."},"message":{"type":"string","description":"Human-readable outcome detail, e.g. \"Order amended on exchange\", \"Order cannot be amended - status is filled\", or \"Could not amend on the exchange: <reason>\". A success whose row write failed reads \"Order amended on exchange (price not persisted)\"."}}},"TerminalFillVerification":{"type":"object","required":["state","exchange_order_id","reason","target_status","started_at"],"properties":{"state":{"type":"string","enum":["pending","complete"]},"exchange_order_id":{"type":"string","description":"Exact venue-order generation covered by the barrier."},"reason":{"type":"string","enum":["explicit_cancel","venue_absent"]},"target_status":{"type":"string","enum":["cancelled","expired"]},"started_at":{"type":"string","format":"date-time"},"final_filled_quantity":{"type":"string","nullable":true,"description":"Exact venue cumulative, including explicit `0`; required when state is complete."},"verified_at":{"type":"string","format":"date-time","nullable":true,"description":"Receipt completion timestamp; required when state is complete."}}},"ReconciledPositionExposure":{"type":"object","required":["exchange_id","market_id","token_id","outcome","net_size","base_established","market_resolved"],"properties":{"exchange_id":{"type":"string","description":"Canonical venue identity.","example":"kalshi"},"market_id":{"type":"string"},"token_id":{"type":"string"},"outcome":{"type":"string","nullable":true},"net_size":{"type":"string","description":"Exact signed post-fold position size as a decimal string, including zero."},"base_established":{"type":"boolean","description":"Explicit proof that the durable position base was established. A returned reconciliation authority always sets this to true; clients must fail closed when it is absent or false during rolling deployment."},"market_resolved":{"type":"boolean","nullable":true,"description":"Tri-state resolution authority. Null means resolution was not proven and must not be interpreted as false."}}},"OrderCancelBatchRequest":{"type":"object","description":"Body for `POST /orders/cancel-batch`.","required":["order_ids"],"properties":{"order_ids":{"type":"array","description":"Internal order ids to cancel. Deduplicated server-side; must be non-empty and at most 100 entries.","maxItems":100,"items":{"type":"string","format":"uuid"}}}},"OrderCancelBatchFailure":{"type":"object","description":"One order the venue refused to cancel.","required":["order_id","exchange_order_id","reason"],"properties":{"order_id":{"type":"string","format":"uuid"},"exchange_order_id":{"type":"string","description":"The venue-side order id that was rejected."},"reason":{"type":"string","description":"Venue-reported reason the cancel was refused."}}},"OrderCancelBatchResponse":{"type":"object","description":"Response for `POST /orders/cancel-batch`.","required":["success","cancelled_count","noop_count","failed_count","cancelled_order_ids","noop_order_ids","failures"],"properties":{"success":{"type":"boolean","description":"True iff `failed_count == 0`."},"cancelled_count":{"type":"integer"},"noop_count":{"type":"integer","description":"Orders the venue reported as a no-op (e.g. already filled/cancelled at the venue) — still counted as handled, not a failure."},"failed_count":{"type":"integer"},"cancelled_order_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"noop_order_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"failures":{"type":"array","items":{"$ref":"#/components/schemas/OrderCancelBatchFailure"}}}},"OrderCancelAllRequest":{"type":"object","description":"Body for `POST /orders/cancel-all`.","required":["exchange_id"],"properties":{"user_id":{"type":"string","deprecated":true,"description":"Ignored. The authenticated caller's id is always used."},"exchange_id":{"type":"string","description":"Venue to cancel all open orders on.","example":"polymarket"},"market_id":{"type":"string","nullable":true,"description":"Optionally scope the cancel-all to a single market/contract id instead of every open order on the exchange."}}},"OrderCancelAllResponse":{"type":"object","description":"Response for `POST /orders/cancel-all`.","required":["cancelled_count","failed_count","cancelled_order_ids"],"properties":{"cancelled_count":{"type":"integer","description":"Authoritative count of orders the venue reported cancelled."},"failed_count":{"type":"integer","description":"Local rows that failed to update to `cancelled` after the venue confirmed the cancel."},"cancelled_order_ids":{"type":"array","description":"Internal order ids (as strings) whose local status was updated. May be fewer than `cancelled_count` if the venue cancelled more than the locally-tracked active set.","items":{"type":"string"}}}},"OrderFeeQuoteResponse":{"type":"object","description":"Response for `GET /orders/fee-quote`. All numeric fields are decimal strings in USDC. The WebSocket `subscribe_fee_quote` stream emits a near-identical frame, but it omits `venue_reserve_fee_usdc` — do not treat the two as interchangeable. See `execution-ws.asyncapi.yaml`.","required":["avg_price_usdc","filled_size","requested_size","sufficient_liquidity","notional_usdc","platform_fee_usdc","exchange_fee_usdc","venue_reserve_fee_usdc","total_fee_usdc","total_cost_usdc","pricing_unavailable","is_estimate","funding_tier","bridge_fee_usdc","bridge_eta_p50_ms","bridge_eta_p95_ms","bridge_route_label","bridge_min_txs","bridge_eta_state","bridge_quote_unavailable","bridge_beta"],"properties":{"avg_price_usdc":{"type":"string","description":"Size-weighted executable price (VWAP for a market order), or the limit price for a limit order.","example":"0.52"},"filled_size":{"type":"string","description":"Size the book can actually fill; equals `requested_size` when liquidity suffices.","example":"100"},"requested_size":{"type":"string","description":"The size the caller asked to quote (echoes `quantity`).","example":"100"},"sufficient_liquidity":{"type":"boolean","description":"False when the book cannot fill the full `requested_size`."},"notional_usdc":{"type":"string","description":"`filled_size × avg_price_usdc`."},"platform_fee_usdc":{"type":"string","description":"Kairos platform fee, based on the caller's fee tier. `\"0\"` if the tier lookup failed (fails open, never a phantom rate)."},"exchange_fee_usdc":{"type":"string","description":"Venue-specific fee the caller is expected to pay. `\"0\"` on venues that only charge takers when this quote is a resting maker order."},"venue_reserve_fee_usdc":{"type":"string","description":"Fee the VENUE actually reserves to accept the order — distinct from `exchange_fee_usdc`. Some venues (Polymarket CLOB) can't know a resting limit will stay maker, so they reserve the taker estimate at placement regardless; buy-affordability checks must size off this field, not `exchange_fee_usdc`."},"total_fee_usdc":{"type":"string","description":"`platform_fee_usdc + exchange_fee_usdc`."},"exchange_fee_note":{"type":"string","nullable":true,"description":"Human-readable note on the exchange fee, e.g. \"1.8% taker fee\"."},"total_cost_usdc":{"type":"string","description":"All-in cost — for a buy, `notional + fees`; for a sell, `proceeds = notional − fees`."},"pricing_unavailable":{"type":"boolean","description":"`true` when no executable price was available (no client price and no fresh orderbook) — every other numeric field is a placeholder `\"0\"` and MUST NOT be rendered as a real quote."},"is_estimate":{"type":"boolean","description":"Always `true` — this is a display estimate; the authoritative fee is computed at fill time."},"funding_tier":{"type":"string","enum":["t0_local","t1_prepositioned","t2_bridge","reject"],"description":"Which rails run to fund this order. `t0_local` means no collateral moves; only `t2_bridge` carries a bridge fee. It names the movement, not whether the balance suffices."},"bridge_fee_usdc":{"type":"string","nullable":true,"description":"Bridge fee in USDC. `\"0\"` below `t2_bridge`, and `null` exactly when `bridge_quote_unavailable` is `true` — an unknown fee is never rendered as `$0.00`. This fee is already included in `total_cost_usdc`, and buy-affordability checks must include it too."},"bridge_eta_p50_ms":{"type":"integer","nullable":true,"description":"Median bridge fill time from Kairos's own completed intents, never a provider estimate. Present only once the route has ≥1,000 samples and its p90 ETA error is within ±10s."},"bridge_eta_p95_ms":{"type":"integer","nullable":true,"description":"95th-percentile bridge fill time from Kairos's own completed intents. Present under the same gate as `bridge_eta_p50_ms`."},"bridge_route_label":{"type":"string","nullable":true,"description":"The route in the user's words, naming the token the rail DELIVERS, e.g. \"BNB USDT → Polymarket pUSD via Relay\". Absent when nothing bridges."},"bridge_min_txs":{"type":"integer","nullable":true,"description":"On-chain transactions the bridge route needs, so a client can price the signing path. Absent when nothing bridges."},"bridge_eta_state":{"type":"string","nullable":true,"enum":["measured","not_yet_measured"],"description":"Whether the ETA above is a measurement or an admission that the route has not earned one. Absent when nothing bridges."},"bridge_quote_unavailable":{"type":"boolean","description":"`true` when a bridge is needed and no quote landed. Consumers MUST render a \"routing…\" state and disable submit rather than showing any fee; `bridge_fee_usdc` is `null` in this state."},"bridge_beta":{"type":"boolean","description":"`true` while tier-2 bridge funding is behind a flag, so the bridge row can be labelled beta."}}},"OrderPositionExposure":{"type":"object","description":"A single open position, from `positions[]` in the exposure response.","required":["token_id","market_id","holding_wallet","net_size","available_to_sell","reserved","avg_entry_price_bps","realized_pnl","last_trade_at"],"properties":{"token_id":{"type":"string"},"market_id":{"type":"string"},"outcome":{"type":"string","nullable":true},"holding_wallet":{"type":"string"},"net_size":{"type":"string","description":"Current signed size (decimal string), positive = long.","example":"100"},"available_to_sell":{"type":"string","description":"`net_size` minus outstanding sell reservations — what a new SELL can reserve right now."},"reserved":{"type":"string","description":"Size committed to in-flight sells: `net_size - available_to_sell`."},"avg_entry_price_bps":{"type":"integer","description":"Average entry price in basis points (price × 10000).","example":5200},"realized_pnl":{"type":"string","description":"Realized PnL to date, decimal string."},"last_trade_at":{"type":"string","format":"date-time"}}},"OrderResolvedPositionExposure":{"type":"object","description":"A held position whose market has resolved, from `resolved[]` in the exposure response.","required":["token_id","market_id","holding_wallet","net_size","redeemable"],"properties":{"token_id":{"type":"string"},"market_id":{"type":"string"},"outcome":{"type":"string","nullable":true},"holding_wallet":{"type":"string"},"net_size":{"type":"string","description":"Size still on the books for this resolved token. Informational only — may lag zero for a settled loser."},"redeemable":{"type":"boolean","description":"`true` = won (claimable via redeem); `false` = lost."}}},"OrderPositionsExposureResponse":{"type":"object","description":"Response for `GET /positions/exposure`.","required":["positions","closed_token_ids","closed","resolved"],"properties":{"positions":{"type":"array","items":{"$ref":"#/components/schemas/OrderPositionExposure"}},"closed_token_ids":{"type":"array","description":"Token ids the store positively holds at `net_size == 0` (folded flat, e.g. a just-settled sell). Absence from both this list and `positions`/`resolved` means \"no current opinion.\" Carries no venue, so on a cell serving several it cannot say which venue's position to retire — read `closed` instead.","items":{"type":"string"}},"closed":{"type":"array","description":"The same retirements as `closed_token_ids`, each scoped to the venue that owns it.","items":{"$ref":"#/components/schemas/OrderClosedPositionExposure"}},"resolved":{"type":"array","items":{"$ref":"#/components/schemas/OrderResolvedPositionExposure"}}}},"OrderClosedPositionExposure":{"type":"object","description":"One retirement, scoped to the venue that owns it.","required":["exchange_id","token_id"],"properties":{"exchange_id":{"type":"string","description":"Canonical execution venue id. Token and market ids are unique only within a venue.","example":"hyperliquid"},"token_id":{"type":"string","description":"Outcome-token id the venue holds at `net_size == 0`."}}},"OrderIntentPayload":{"type":"object","description":"The unsigned order intent — `intent` field of `OrderIntentRequest`, mirrored inside the one-RTT WSS `submit_signed_order` command.","required":["token_id","side","price","size","time_in_force","neg_risk","owner_address","signature_type"],"properties":{"token_id":{"type":"string","description":"Polymarket outcome-token id (uint256, decimal string).","example":"71360012345678901234567890123456789012345678901234567890123456"},"side":{"$ref":"#/components/schemas/OrderSide"},"price":{"type":"string","description":"Decimal string, limit price in `[tick, 1]`. Caller is responsible for snapping to the market's tick — the server does not re-snap, so the digest you compute locally matches what the server recomputes.","example":"0.52"},"size":{"type":"string","description":"Decimal string, order size in shares.","example":"100"},"time_in_force":{"$ref":"#/components/schemas/OrderTimeInForce"},"post_only":{"type":"boolean","description":"Maker-only. NOT part of the signed EIP-712 digest — it rides on the outer venue payload. Requires a resting time-in-force (`GTC` or `GTD`); anything else is rejected with `400 post_only requires a resting time-in-force (GTC or GTD), not <tif>`.","default":false},"expiration_unix_secs":{"type":"integer","nullable":true,"description":"UNIX seconds expiration. Required if (and only meaningful when) `time_in_force=GTD` — a GTD intent without it is rejected, and one already in the past is rejected with `400 expiration_unix_secs is in the past for this GTD order`. On Polymarket this value is NOT part of the signed `Order` struct; it travels on the outer payload."},"neg_risk":{"type":"boolean","description":"Whether the market is a neg-risk market — selects the EIP-712 verifying contract. On Polymarket the caller is expected to know this. On Predict.fun the value is CROSS-CHECKED against authoritative market metadata and a mismatch is a `400`, never a silent correction."},"owner_address":{"type":"string","description":"0x-checksummed EOA address that will sign the digest. For `signature_type=0` (EOA) this MUST equal both the order maker and signer. On Predict.fun it must additionally be your registered Predict.fun trading wallet.","example":"0x0000000000000000000000000000000000dEaD"},"signer_address":{"type":"string","nullable":true,"description":"Signer address. On this lane it must equal `owner_address` (or be omitted); a different value is rejected with `400 signer_address must equal owner_address for signature_type 0 (EOA)`."},"signature_type":{"type":"integer","description":"Signature-type discriminant. Only `0` (EOA) is accepted on this lane — the intake validator rejects anything else with `400 only signature_type 0 (EOA) is supported on the external-signing lane`. (The underlying Polymarket builder also understands `2` = Poly1271, but the HTTP/WSS handlers never let it through, so verification here is always raw `ecrecover` and never EIP-1271.)","enum":[0],"example":0},"salt":{"type":"string","nullable":true,"description":"uint256 decimal string. If omitted, the server generates one and returns it in `unsigned_payload`. REQUIRED (along with `timestamp_ms`) if you build and sign the order fully client-side over the one-RTT WSS `submit_signed_order` command instead of this two-RTT REST flow."},"timestamp_ms":{"type":"string","nullable":true,"description":"uint256 decimal string, order timestamp in milliseconds (CLOB V2 uses this in place of a nonce). If omitted, the server stamps it. See `salt` for when it's required."}}},"OrderEipDomain":{"type":"object","description":"EIP-712 domain separator fields.","required":["name","version","chain_id","verifying_contract"],"properties":{"name":{"type":"string","example":"Polymarket CTF Exchange"},"version":{"type":"string","example":"2"},"chain_id":{"type":"integer","example":137},"verifying_contract":{"type":"string","example":"0xE111180000d2663C0091e4f400237545B87B996B"}}},"OrderUnsignedPayload":{"type":"object","description":"Full EIP-712 typed-data payload — pass directly to `eth_signTypedData` as an alternative to raw-hash-signing `eip712_digest_hex`.","required":["domain","primary_type","types","message","eip712_digest_hex"],"properties":{"domain":{"$ref":"#/components/schemas/OrderEipDomain"},"primary_type":{"type":"string","example":"Order"},"types":{"type":"object","description":"The full EIP-712 type set for `eth_signTypedData`.","additionalProperties":true},"message":{"type":"object","description":"The order struct's field values, as they will be hashed.","additionalProperties":true},"eip712_digest_hex":{"type":"string","description":"0x-prefixed 32-byte digest. Sign this raw (no EIP-191 prefix) to produce a signature identical to signing `message` via `eth_signTypedData`.","example":"0x9a1c2b3d4e5f60718293a4b5c6d7e8f90112233445566778899aabbccddeeff0"}}},"OrderIntentRequest":{"type":"object","description":"Body for `POST /v2/orders/intent`.","required":["intent"],"properties":{"provider":{"$ref":"#/components/schemas/OrderExternalProvider"},"intent":{"$ref":"#/components/schemas/OrderIntentPayload"},"market_id":{"type":"string","nullable":true,"description":"Condition id — METADATA ONLY, not part of the signed digest, but REQUIRED in practice. The server resolves `intent.token_id` against it and rejects a blank value with `400 market_id is required for external orders`, a token that does not belong to it with `400 token_id does not belong to the supplied market_id`. Max 256 chars."},"outcome":{"type":"string","nullable":true,"description":"REQUIRED — outcome label (e.g. `\"Yes\"`/`\"No\"`); metadata only, not signed. A missing/empty value is `400 outcome is required for external orders`, and a label that does not match the resolved token is `400 token_id does not match the supplied outcome`. Max 64 chars.","example":"Yes"}}},"OrderExternalProvider":{"type":"string","description":"Target venue for the external-signing lane. Only these two venues are implemented; Kalshi and Solana variants exist in the internal types but are not reachable on `/v2/orders/*`. The one-RTT WebSocket `submit_signed_order` command and the whole `/v2/onchain/*` lane are Polymarket-only and have no `provider` field.","enum":["polymarket","predictfun"],"default":"polymarket","example":"polymarket"},"OrderIntentResponse":{"type":"object","description":"Response for `POST /v2/orders/intent`.","required":["payload_id","unsigned_payload","eip712_digest_hex","expires_at_us"],"properties":{"payload_id":{"type":"string","format":"uuid","description":"Single-use handle for this stored intent. Pass back to `POST /v2/orders/submit`."},"unsigned_payload":{"$ref":"#/components/schemas/OrderUnsignedPayload"},"eip712_digest_hex":{"type":"string","description":"Convenience copy of `unsigned_payload.eip712_digest_hex` — the digest to sign."},"expires_at_us":{"type":"integer","format":"int64","description":"Wall-clock expiry, UNIX microseconds (60 s out by default, `EXTERNAL_SIGNING_INTENT_TTL_SECS`). The stored intent is claimed with an atomic Redis `GETDEL` at the very top of `/submit`, BEFORE any signature verification — so it is consumed by ANY submit attempt, not only a successful one. A `/submit` that fails verification burns the `payload_id`; retrying needs a fresh `POST /v2/orders/intent`."}}},"OrderSubmitSignedRequest":{"type":"object","description":"Body for `POST /v2/orders/submit`.","required":["payload_id","signature_hex"],"properties":{"payload_id":{"type":"string","format":"uuid","description":"The `payload_id` returned by `POST /v2/orders/intent`. Single-use — claimed atomically on the first `/submit` call."},"signature_hex":{"type":"string","description":"0x-prefixed 65-byte secp256k1 signature (`r || s || v`) over `eip712_digest_hex`.","example":"0x1234...1b"}}},"OrderSubmitSignedResponse":{"type":"object","description":"Response for `POST /v2/orders/submit` (and the equivalent one-RTT WSS `submit_signed_order` command).","required":["order_id","status"],"properties":{"order_id":{"type":"string","format":"uuid","description":"Internal Kairos order id — usable for `GET /orders/{order_id}` and the cancel endpoints."},"exchange_order_id":{"type":"string","nullable":true,"description":"Venue-assigned order handle. An EMPTY venue id is treated as a failed submission and returned as `400 venue returned an empty order id; order not tracked`, so on a `200` this is in practice always populated; it is `null` only on the idempotent-replay response path."},"status":{"type":"string","description":"The venue's immediate result string, passed through verbatim — Kairos does not normalize or validate it. Polymarket's observed values are `matched`, `live` and `delayed`; `unmatched` appears on the fill-polling path. Treat this as an open string, not a closed enum.","example":"matched"}}},"OrderOnchainIntentRequest":{"type":"object","description":"Body for `POST /v2/onchain/intent`. You supply the nonce and gas price yourself — the server makes no RPC call while building the intent (no nonce fetch, no gas quote, no balance preflight).","required":["op","owner_address","nonce","gas_price_wei"],"properties":{"op":{"type":"string","description":"Which on-chain operation to build. `approvals` builds the one-time collateral + CTF approvals; `redeem` claims a resolved position; `split`/`merge` convert between collateral and a complete outcome-token set; `unwrap_wcol` unwraps wrapped collateral.","enum":["approvals","redeem","split","merge","unwrap_wcol"],"example":"redeem"},"owner_address":{"type":"string","description":"The EOA that will sign and broadcast. Must be a wallet registered to the authenticated caller."},"condition_id":{"type":"string","nullable":true,"description":"CTF conditionId. Required for `redeem`, `split` and `merge`."},"neg_risk":{"type":"boolean","nullable":true,"description":"Whether the market is a neg-risk market (selects the adapter contract)."},"collateral_address":{"type":"string","nullable":true},"amount":{"type":"string","nullable":true,"description":"Amount in wei, decimal string. Required for `split` and `merge`; must be > 0."},"nonce":{"type":"integer","format":"int64","description":"The signing EOA's next transaction nonce. Caller-supplied — the server never queries the chain for it."},"gas_price_wei":{"type":"string","description":"Gas price in wei, decimal string. Must be > 0. Caller-supplied."},"gas_limit":{"type":"integer","format":"int64","nullable":true},"wcol_amount":{"type":"string","nullable":true,"description":"Amount in wei, decimal string. Required for `unwrap_wcol`; must be > 0."}}},"OrderUnsignedTx":{"type":"object","description":"One pinned unsigned transaction to sign and hand back to `POST /v2/onchain/submit`.","required":["description","to","data_hex","value","nonce","gas","gas_price","chain_id","signing_digest_hex","unsigned_rlp_hex"],"properties":{"description":{"type":"string","description":"Human-readable label for this leg (e.g. which approval it grants)."},"to":{"type":"string"},"data_hex":{"type":"string"},"value":{"type":"string"},"nonce":{"type":"integer","format":"int64"},"gas":{"type":"string"},"gas_price":{"type":"string"},"chain_id":{"type":"integer","description":"Always `137` — this lane is Polygon/Polymarket-only.","example":137},"signing_digest_hex":{"type":"string","description":"The legacy EIP-155 sighash (`keccak256` of the unsigned RLP). Sign these raw 32 bytes — no EIP-191 prefix. The server normalizes `v` to `35 + 2·chain_id + parity` itself."},"unsigned_rlp_hex":{"type":"string"}}},"OrderOnchainIntentResponse":{"type":"object","description":"Response for `POST /v2/onchain/intent`.","required":["payload_id","transactions","expires_at_us"],"properties":{"payload_id":{"type":"string","format":"uuid","description":"Single-use handle. Pass back to `POST /v2/onchain/submit`."},"transactions":{"type":"array","description":"The transactions to sign, in the order they must be broadcast (ascending nonce).","items":{"$ref":"#/components/schemas/OrderUnsignedTx"}},"expires_at_us":{"type":"integer","format":"int64","description":"Wall-clock expiry, UNIX microseconds. TTL is 300 s here (longer than the 60 s order-intent TTL, because signing several transactions on a hardware wallet takes longer)."},"requires_followup":{"type":"string","nullable":true,"description":"Reserved. Always absent on a newly-created intent."}}},"OrderOnchainSubmitRequest":{"type":"object","description":"Body for `POST /v2/onchain/submit`.","required":["payload_id","signatures"],"properties":{"payload_id":{"type":"string","format":"uuid"},"signatures":{"type":"array","description":"One 0x-hex 65-byte signature per transaction, in the same order as `transactions[]` from the intent. The count must match exactly.","items":{"type":"string"}}}},"OrderOnchainSubmitResponse":{"type":"object","description":"Response for `POST /v2/onchain/submit`.","required":["transaction_hashes","status"],"properties":{"transaction_hashes":{"type":"array","items":{"type":"string"}},"status":{"type":"string","description":"Always `confirmed` on a 200 — each transaction is broadcast and waited on (90 s) in nonce order before the next.","enum":["confirmed"]}}},"OrderHealthResponse":{"type":"object","description":"Response for `GET /health`. Always returned with HTTP 200 — a degraded backing store flips the fields rather than the status code, because this is the load balancer's liveness probe.","required":["status","version","healthy"],"properties":{"status":{"type":"string","enum":["ok","degraded"]},"version":{"type":"string"},"healthy":{"type":"boolean","description":"`false` (with `status: degraded`) when the Redis ping failed."}}},"OrderSupportedOrderTypes":{"type":"object","description":"Which order types the venue's adapter implements.","required":["market","limit","stop_loss","stop_limit","take_profit","trailing_stop"],"properties":{"market":{"type":"boolean"},"limit":{"type":"boolean"},"stop_loss":{"type":"boolean"},"stop_limit":{"type":"boolean"},"take_profit":{"type":"boolean"},"trailing_stop":{"type":"boolean"}}},"OrderExchangeCapabilities":{"type":"object","description":"What a venue supports. This is the authoritative source for the per-venue constraints\n`POST /orders` enforces — a `time_in_force` outside `supported_tif`, or `post_only` on a venue\nwith `supports_post_only: false`, is rejected with `400`, never silently downgraded.\n\nCurrent production values:\n\n| Venue | `supported_tif` | `supports_post_only` | `min_tick_size` | `max_price` | `settlement_currency` |\n|---|---|---|---|---|---|\n| `polymarket` | GTC, GTD, FOK, FAK, IOC | true | 0.01 | 1 | USDC |\n| `predictfun` | GTC, GTD, FOK, FAK, IOC | true | 0.001 | 1 | USDT |\n| `kalshi` | GTC, GTD, IOC, FAK, FOK | true | 0.01 | 1 | USD |\n| `hyperliquid` | GTC, GTD, IOC, FAK, FOK | true (expressed venue-side as the `Alo` TIF) | 0.0001 | none | USDC |\n| `opinion` | GTC only | false | 0.01 | 1 | USDC |\n\n`kalshi_offchain` is a deprecated alias that canonicalizes to `kalshi`.","required":["exchange_id","display_name","supported_order_types","supported_tif","requires_allowances","supports_redemption","has_outcome_tokens","supports_user_websocket","supports_cancel_all","supports_batch_orders","min_tick_size","min_order_size","fee_model","settlement_currency","autonomous_settlement","settlement_deferred_fill","is_active"],"properties":{"exchange_id":{"type":"string","description":"The CANONICAL venue id — may differ from the id you requested (`kalshi_offchain` resolves to `kalshi`)."},"display_name":{"type":"string"},"supported_order_types":{"$ref":"#/components/schemas/OrderSupportedOrderTypes"},"supported_tif":{"type":"array","items":{"$ref":"#/components/schemas/OrderTimeInForce"}},"supports_post_only":{"type":"boolean","default":false},"requires_allowances":{"type":"boolean"},"supports_redemption":{"type":"boolean"},"has_outcome_tokens":{"type":"boolean"},"supports_user_websocket":{"type":"boolean"},"supports_cancel_all":{"type":"boolean","description":"When `false`, `POST /orders/cancel-all` is not available for this venue."},"supports_batch_orders":{"type":"boolean"},"min_tick_size":{"type":"string","description":"Decimal string."},"min_order_size":{"type":"string","description":"Decimal string."},"max_order_size":{"type":"string","nullable":true},"fee_model":{"type":"string","enum":["zero_fee","maker_rebate","tiered_schedule","fixed_bps"]},"maker_fee_bps":{"type":"integer","nullable":true},"taker_fee_bps":{"type":"integer","nullable":true},"chain_id":{"type":"string","nullable":true,"description":"Decimal string (e.g. `\"137\"`), or null for off-chain venues."},"settlement_currency":{"type":"string"},"autonomous_settlement":{"type":"boolean"},"settlement_deferred_fill":{"type":"boolean"},"sell_quantity_decimals":{"type":"integer","nullable":true,"description":"Decimal places a SELL quantity is truncated to before submission."},"max_price":{"type":"string","nullable":true,"description":"Decimal string. The ceiling `price` and `trigger_price` are validated against."},"is_active":{"type":"boolean"},"ws_fill_authoritative":{"type":"boolean","default":false},"ws_fills_include_exchange_fee":{"type":"boolean","default":false},"supports_native_amend":{"type":"boolean","default":false,"description":"Venue-native amend exists. FIX replace otherwise uses durable synthetic replacement."}}},"OrderExchangeInfo":{"type":"object","description":"Response for `GET /exchanges/{exchange_id}`.","required":["id","name","is_active","capabilities"],"properties":{"id":{"type":"string","description":"Echoes the id you asked for verbatim — NOT canonicalized. Use `capabilities.exchange_id` for the canonical value."},"name":{"type":"string"},"is_active":{"type":"boolean"},"capabilities":{"$ref":"#/components/schemas/OrderExchangeCapabilities"}}},"OrderSetAllowancesRequest":{"type":"object","description":"Body for `POST /exchanges/{exchange_id}/allowances`. All three identity fields are required by the schema but are VALIDATED against the authenticated caller, never trusted — they can confirm authority, never grant it.","required":["user_id","turnkey_org_id","wallet_address"],"properties":{"user_id":{"type":"string"},"turnkey_org_id":{"type":"string"},"wallet_address":{"type":"string"}}},"OrderSetAllowancesResponse":{"type":"object","required":["success"],"properties":{"usdc_tx_hash":{"type":"string","nullable":true},"ctf_tx_hash":{"type":"string","nullable":true},"success":{"type":"boolean"}}},"OrderRouteQuoteLeg":{"type":"object","required":["provider","market_id","token_id","quantity","limit_price","effective_limit","est_notional","est_fee","below_min_notional"],"properties":{"provider":{"type":"string"},"market_id":{"type":"string"},"token_id":{"type":"string"},"quantity":{"type":"string"},"limit_price":{"type":"string"},"effective_limit":{"type":"string"},"est_notional":{"type":"string"},"est_fee":{"type":"string"},"below_min_notional":{"type":"boolean"}}},"OrderRouteQuoteResponse":{"type":"object","description":"Response for `GET /orders/route-quote`. A quote that cannot be routed is still a `200` with `routed` set false — check the flag, not the status code.","required":["routed","notes","legs","requested","planned","unfilled"],"properties":{"routed":{"type":"boolean"},"link_id":{"type":"string","format":"uuid","nullable":true},"link_title":{"type":"string","nullable":true},"notes":{"type":"array","items":{"type":"string"}},"legs":{"type":"array","items":{"$ref":"#/components/schemas/OrderRouteQuoteLeg"}},"requested":{"type":"string"},"planned":{"type":"string"},"unfilled":{"type":"string"},"blended_effective":{"type":"string","nullable":true},"stop":{"type":"string","nullable":true,"description":"Why planning stopped.","enum":["filled","exhausted","effective_cap"]}}},"OrderRouteFeeModelView":{"type":"object","required":["model","estimated"],"properties":{"model":{"type":"string","enum":["curve","min_side_bps","none"]},"rate_ppm":{"type":"integer","format":"int64","nullable":true},"bps":{"type":"integer","nullable":true},"estimated":{"type":"boolean"}}},"OrderRouteFeesResponse":{"type":"object","description":"Response for `GET /orders/route-fees`. An unknown link is a `200` with `routed` set false and an empty `legs`.","required":["routed","legs"],"properties":{"routed":{"type":"boolean","description":"`true` only when at least two routable legs were resolved."},"legs":{"type":"array","items":{"type":"object","required":["provider","market_id","stream_key"],"properties":{"provider":{"type":"string"},"market_id":{"type":"string"},"stream_key":{"type":"string"},"yes":{"oneOf":[{"$ref":"#/components/schemas/OrderRouteFeeModelView"},{"type":"null"}]},"no":{"oneOf":[{"$ref":"#/components/schemas/OrderRouteFeeModelView"},{"type":"null"}]}}}}}},"OrderRouteBuyRequest":{"type":"object","description":"Body for `POST /orders/route-buy`.","required":["provider","market_id","quantity"],"properties":{"provider":{"type":"string"},"market_id":{"type":"string"},"side":{"type":"string","nullable":true,"description":"`yes` or `no`. Supply this or `outcome`.","enum":["yes","no"]},"outcome":{"type":"string","nullable":true,"description":"Venue-native outcome label, as an alternative to `side`."},"quantity":{"type":"string","description":"Decimal string, must be > 0."},"slippage_cents":{"type":"integer","nullable":true,"description":"Must be in `[1, 99]` if provided."}}},"OrderRouteBuyLegResult":{"type":"object","required":["provider","planned_qty","planned_limit_price"],"properties":{"provider":{"type":"string"},"order_id":{"type":"string","format":"uuid","nullable":true,"description":"Null when this leg failed to submit — see `error`."},"planned_qty":{"type":"string"},"planned_limit_price":{"type":"string"},"error":{"type":"string","nullable":true,"description":"Per-leg submission failure, reported at HTTP 200. An opaque debug rendering of the underlying order error — do not parse it."}}},"OrderRouteBuyResponse":{"type":"object","description":"Response for `POST /orders/route-buy`. Individual legs can fail while the request succeeds — always inspect `legs[].error`.","required":["route_order_id","status","requested","planned","legs","notes"],"properties":{"route_order_id":{"type":"string","format":"uuid"},"status":{"type":"string","description":"Always `submitting` — children are dispatched asynchronously. Poll `GET /orders/routes`.","enum":["submitting"]},"requested":{"type":"string"},"planned":{"type":"string"},"legs":{"type":"array","items":{"$ref":"#/components/schemas/OrderRouteBuyLegResult"}},"notes":{"type":"array","items":{"type":"string"}}}},"OrderRouteCloseRequest":{"type":"object","description":"Body for `POST /orders/route-close`.","required":["provider","market_id"],"properties":{"provider":{"type":"string"},"market_id":{"type":"string"},"side":{"type":"string","nullable":true,"enum":["yes","no"]},"outcome":{"type":"string","nullable":true},"slippage_cents":{"type":"integer","nullable":true,"description":"Defaults to `5`. Must be in `[1, 99]` if provided.","default":5},"leg_caps":{"type":"array","nullable":true,"description":"Per-venue size caps. FAIL-CLOSED: when `leg_caps` is present, any leg with no matching entry is EXCLUDED from the close entirely rather than closed in full.","items":{"type":"object","required":["provider","quantity"],"properties":{"provider":{"type":"string"},"quantity":{"type":"string"}}}}}},"OrderRouteCloseResponse":{"type":"object","description":"Response for `POST /orders/route-close`.","required":["route_order_id","status","closing","legs","notes"],"properties":{"route_order_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["submitting"]},"closing":{"type":"string"},"legs":{"type":"array","items":{"type":"object","required":["provider","holdings"],"properties":{"provider":{"type":"string"},"order_id":{"type":"string","format":"uuid","nullable":true},"holdings":{"type":"string"},"floor_price":{"type":"string","nullable":true},"error":{"type":"string","nullable":true,"description":"Per-leg failure at HTTP 200, e.g. `no bid available for floor — leg not sold` or `no floor computed`."}}}},"notes":{"type":"array","items":{"type":"string"}}}},"OrderRouteView":{"type":"object","description":"One routed parent order, from `GET /orders/routes`.","required":["id","provider","market_id","action","side","requested_qty","status","filled_qty","created_at","legs"],"properties":{"id":{"type":"string","format":"uuid"},"market_link_id":{"type":"string","format":"uuid","nullable":true},"provider":{"type":"string"},"market_id":{"type":"string"},"action":{"type":"string","enum":["buy","sell"]},"side":{"type":"string"},"requested_qty":{"type":"string"},"status":{"type":"string"},"filled_qty":{"type":"string"},"avg_fill_price_bps":{"type":"integer","nullable":true},"created_at":{"type":"string","description":"Naive local timestamp — serialized WITHOUT a timezone offset, so it is not an RFC 3339 instant. Unlike `Order.created_at`."},"completed_at":{"type":"string","nullable":true,"description":"Naive local timestamp, same caveat as `created_at`."},"legs":{"type":"array","items":{"type":"object","required":["provider","planned_qty","planned_limit_price_bps"],"properties":{"provider":{"type":"string"},"market_id":{"type":"string","nullable":true},"planned_qty":{"type":"string"},"planned_limit_price_bps":{"type":"integer"},"order_id":{"type":"string","format":"uuid","nullable":true},"order_status":{"type":"string","nullable":true},"filled_size":{"type":"string","nullable":true},"avg_fill_price_bps":{"type":"integer","nullable":true},"token_id":{"type":"string","nullable":true},"outcome":{"type":"string","nullable":true},"error":{"type":"string","nullable":true}}}}}},"OrderCredentialsInvalidateRequest":{"type":"object","description":"Body for `POST /credentials/invalidate`.","required":["user_id"],"properties":{"user_id":{"type":"string","description":"Must equal the authenticated caller's id — a mismatch is a `403`."},"exchange_id":{"type":"string","nullable":true,"description":"Omit to invalidate the cached credentials for EVERY registered exchange. NOT canonicalized, so a deprecated alias such as `kalshi_offchain` may `404`."}}},"OrderCredentialsInvalidateResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"Always `true` on the success path."}}},"OrderComboExecuteRequest":{"type":"object","description":"Body for `POST /combo/execute`. NOTE: the whole `/combo/*` family uses **camelCase** field names, where the Orders endpoints are snake_case. Do not assume one convention across this API.","required":["legPositionIds","notionalUsd"],"properties":{"legPositionIds":{"type":"array","description":"The YES-outcome position id of each leg market. Minimum 2, maximum 10 legs.","minItems":2,"maxItems":10,"items":{"type":"string"}},"notionalUsd":{"type":"number","format":"double","description":"USD notional to spend on the combo (e.g. `10.0` = $10). Must be finite and > 0, and large enough to round to a non-zero six-decimal amount.","example":10},"maxPriceCents":{"type":"integer","nullable":true,"description":"Reject the quote if the blended price exceeds this, in cents per share (e.g. `60` = $0.60). Strongly recommended — without it you accept whatever the maker quotes.","example":60}}},"OrderComboExecuteResponse":{"type":"object","description":"Response for `POST /combo/execute`.","required":["rfqId","quoteId","yesPositionId","blendedPriceE6","totalRequiredE6","status"],"properties":{"rfqId":{"type":"string"},"quoteId":{"type":"string"},"conditionId":{"type":"string","nullable":true},"yesPositionId":{"type":"string","description":"The combo's synthetic YES outcome position id."},"blendedPriceE6":{"type":"string","description":"Blended maker price as a six-decimal fixed-point integer string (e.g. `\"16393\"` = 0.016393). NOT a decimal — divide by 1,000,000.","example":"16393"},"totalRequiredE6":{"type":"string","description":"pUSD spent, six-decimal fixed-point integer string."},"status":{"type":"string","description":"The RFQ gateway's settlement status, passed through verbatim."},"txHash":{"type":"string","nullable":true}}},"OrderComboQuoteRequest":{"type":"object","description":"Body for `POST /combo/quote`.","required":["legPositionIds","notionalUsd"],"properties":{"legPositionIds":{"type":"array","minItems":2,"maxItems":10,"items":{"type":"string"}},"notionalUsd":{"type":"number","format":"double","example":10}}},"OrderComboQuoteResponse":{"type":"object","description":"Response for `POST /combo/quote`.","required":["yesPositionId","blendedPriceE6","totalRequiredE6"],"properties":{"yesPositionId":{"type":"string"},"conditionId":{"type":"string","nullable":true},"blendedPriceE6":{"type":"string","description":"Six-decimal fixed-point integer string."},"totalRequiredE6":{"type":"string","description":"pUSD the taker must hold, six-decimal fixed-point integer string."}}},"OrderComboLeg":{"type":"object","required":["legPositionId","outcomeLabel","currentPrice","legStatus","title","slug","outcome","imageUrl"],"properties":{"legPositionId":{"type":"string"},"outcomeLabel":{"type":"string"},"currentPrice":{"type":"string","description":"Live leg price in `0..1`, as an upstream-provided string."},"legStatus":{"type":"string","description":"Authoritative per-leg resolution. Use this — never a price heuristic — to mark a leg won/lost/pending.","enum":["OPEN","RESOLVED_WIN","RESOLVED_LOSS"]},"title":{"type":"string"},"slug":{"type":"string"},"outcome":{"type":"string"},"imageUrl":{"type":"string"}}},"OrderComboPosition":{"type":"object","required":["comboConditionId","comboPositionId","sharesBalance","entryCostUsdc","totalCostUsdc","realizedPayoutUsdc","status","redeemable","firstEntryAt","legsTotal","legsResolved","legsPending","legs"],"properties":{"comboConditionId":{"type":"string","description":"31-byte hex. Pass this as `comboConditionId` to `POST /combo/redeem`."},"comboPositionId":{"type":"string"},"sharesBalance":{"type":"string","description":"Shares held (decimal string). A winning combo redeems 1:1 at $1, so this is also the maximum payout in USD."},"entryCostUsdc":{"type":"string"},"totalCostUsdc":{"type":"string"},"realizedPayoutUsdc":{"type":"string"},"status":{"type":"string","description":"Combo-level status from upstream. NOTE: it stays `OPEN` for a won-but-unredeemed combo — do NOT infer redeemability from it. Use `redeemable`."},"redeemable":{"type":"boolean","description":"The single source of truth for \"can redeem now\" — `true` only for a resolved WIN with a non-zero balance. Covers \"not resolved\", \"lost\" and \"already redeemed\" in one flag."},"firstEntryAt":{"type":"string"},"legsTotal":{"type":"integer"},"legsResolved":{"type":"integer"},"legsPending":{"type":"integer"},"legs":{"type":"array","items":{"$ref":"#/components/schemas/OrderComboLeg"}}}},"OrderComboPositionsResponse":{"type":"object","description":"Response for `GET /combo/positions`.","required":["combos"],"properties":{"combos":{"type":"array","description":"Capped at 50 combos, newest/most-valuable first. There is no pagination parameter.","items":{"$ref":"#/components/schemas/OrderComboPosition"}}}},"OrderComboCashOutQuoteRequest":{"type":"object","description":"Body for `POST /combo/cash-out-quote`.","required":["legPositionIds","shares"],"properties":{"legPositionIds":{"type":"array","description":"The combo's leg position ids, from the position's `legs[].legPositionId`.","minItems":2,"maxItems":10,"items":{"type":"string"}},"shares":{"type":"number","format":"double","description":"Shares to price. Must be finite and > 0. Pass the full `sharesBalance` to preview a complete close."}}},"OrderComboCashOutQuoteResponse":{"type":"object","description":"Response for `POST /combo/cash-out-quote`. All amounts are six-decimal fixed-point integer strings.","required":["rfqId","proceedsE6","feeE6","netProceedsE6","blendedPriceE6"],"properties":{"rfqId":{"type":"string"},"proceedsE6":{"type":"string","description":"GROSS pUSD proceeds from the maker quote. The `minProceedsUsd` floor on `POST /combo/cash-out` is checked against this, not against the net."},"feeE6":{"type":"string","description":"Estimated Kairos platform fee on the proceeds."},"netProceedsE6":{"type":"string","description":"Proceeds after the platform fee — what actually lands. This is the number to show in a cash-out preview."},"blendedPriceE6":{"type":"string"}}},"OrderComboCashOutRequest":{"type":"object","description":"Body for `POST /combo/cash-out`.","required":["legPositionIds","shares"],"properties":{"legPositionIds":{"type":"array","minItems":2,"maxItems":10,"items":{"type":"string"}},"shares":{"type":"number","format":"double","description":"Shares to sell. Must be finite and > 0."},"minProceedsUsd":{"type":"number","format":"double","nullable":true,"description":"Reject the cash-out if GROSS proceeds fall below this USD floor (the platform fee is charged after the floor check). Recommended."}}},"OrderComboCashOutResponse":{"type":"object","description":"Response for `POST /combo/cash-out`.","required":["rfqId","sharesSoldE6","proceedsE6","blendedPriceE6","status"],"properties":{"rfqId":{"type":"string"},"sharesSoldE6":{"type":"string"},"proceedsE6":{"type":"string"},"blendedPriceE6":{"type":"string"},"status":{"type":"string"},"txHash":{"type":"string","nullable":true}}},"OrderComboRedeemRequest":{"type":"object","description":"Body for `POST /combo/redeem`.","required":["comboConditionId"],"properties":{"comboConditionId":{"type":"string","description":"The resolved combo's `comboConditionId` (31-byte hex) from `GET /combo/positions`. The amount redeemed is always the position's real on-chain share balance — never caller-supplied — so a wrong or stale value cannot over- or under-redeem."},"outcomeIndex":{"type":"integer","nullable":true,"description":"Winning outcome side; `0` = YES (all legs won). Defaults to `0`.","default":0}}},"OrderComboRedeemResponse":{"type":"object","description":"Response for `POST /combo/redeem`.","required":["batchTxId","amountE6","status"],"properties":{"batchTxId":{"type":"string","description":"The Polymarket relayer batch id for the redemption."},"amountE6":{"type":"string","description":"Amount redeemed, six-decimal fixed-point integer string."},"status":{"type":"string"}}},"OrderMarketLinkLegRef":{"type":"object","required":["provider","market_id"],"properties":{"provider":{"type":"string","description":"Routable venue. Only `polymarket` and `predictfun` are accepted.","enum":["polymarket","predictfun"]},"market_id":{"type":"string","description":"Either the venue-native executor id or the stream-side alias (the web terminal's Polymarket ids are the numeric Gamma form) — both are matched against the index."}}},"OrderCreateMarketLinkRequest":{"type":"object","description":"Body for `POST /orders/market-links`.","required":["legs"],"properties":{"legs":{"type":"array","description":"Exactly two legs, on two DIFFERENT venues.","minItems":2,"maxItems":2,"items":{"$ref":"#/components/schemas/OrderMarketLinkLegRef"}},"title":{"type":"string","nullable":true,"description":"Optional display title. Trimmed, truncated to 512 characters; a blank value falls back to the first leg's market name."},"similarity":{"type":"number","format":"double","nullable":true,"description":"Matcher confidence, carried through for provenance only."},"confirm":{"type":"boolean","description":"`false` (the default) returns a verified preview and writes nothing. `true` inserts the link, and additionally requires `fingerprint`.","default":false},"fingerprint":{"type":"string","nullable":true,"description":"REQUIRED when `confirm` is `true` — the `verification_fingerprint` from the preview. The confirm re-resolves against fresh metadata and requires the result to match what you reviewed, so index drift becomes a `409` rather than a silently different link."}}},"OrderMarketLinkLegPreview":{"type":"object","required":["provider","market_id","stream_key","outcome_yes_label","outcome_no_label","market_name","expires_at"],"properties":{"provider":{"type":"string"},"market_id":{"type":"string","description":"Executor-convention market id (the condition id on Polymarket)."},"stream_key":{"type":"string","description":"The stream-side id clients hold (the numeric Gamma id on Polymarket)."},"outcome_yes_label":{"type":"string"},"outcome_no_label":{"type":"string"},"market_name":{"type":"string"},"expires_at":{"type":"string","description":"`YYYY-MM-DD HH:MM:SS`, as indexed — not RFC 3339."}}},"OrderMarketLinkResponse":{"type":"object","description":"Response for `POST /orders/market-links`.","required":["status","legs","warnings","verification_fingerprint"],"properties":{"status":{"type":"string","description":"`preview` (nothing written), `created` (inserted), or `exists` (a link the caller can already route).","enum":["preview","created","exists"]},"link_id":{"type":"string","format":"uuid","nullable":true,"description":"Null on a `preview`."},"scope":{"type":"string","nullable":true,"description":"On `exists`, whether the existing link is `global` or the caller's own `user` link. Always `user` on `created`; null on `preview`.","enum":["global","user"]},"title":{"type":"string","nullable":true,"description":"Null on an `exists` response."},"legs":{"type":"array","items":{"$ref":"#/components/schemas/OrderMarketLinkLegPreview"}},"warnings":{"type":"array","description":"Non-blocking advisories (venue expiry gaps, resolution-source divergence). Always includes the standing \"venues may resolve on different data sources\" notice.","items":{"type":"string"}},"verification_fingerprint":{"type":"string","description":"16-hex-character hash of the verified identity material (ids, tokens, case-folded outcome labels). Echo it back as `fingerprint` on confirm. Change detection, not security."}}},"OrderMarketLinkErrorResponse":{"type":"object","description":"Error body for `POST /orders/market-links` ONLY. The key is `message`, NOT `error` — this endpoint uses a fourth error shape found nowhere else in this API, and a client written against `OrderErrorResponse` or `OrderSimpleErrorResponse` will silently read `undefined` for the reason. There is no `code` field.","required":["message"],"properties":{"message":{"type":"string","example":"exactly two legs required"}}},"OrderPrepareWalletRequest":{"type":"object","description":"Body for `POST /exchanges/{exchange_id}/prepare-wallet`. All three identity fields are mandatory and are VALIDATED against the authenticated caller — they can confirm authority, never grant it.","required":["user_id","turnkey_org_id","wallet_address"],"properties":{"user_id":{"type":"string"},"turnkey_org_id":{"type":"string"},"wallet_address":{"type":"string","description":"Must be a wallet owned by the authenticated caller."},"wait_for_confirmation":{"type":"boolean","nullable":true,"description":"**Defaults to `true`** — the call blocks until gas sponsorship and allowances are confirmed. Pass `false` to fire-and-forget: the response returns immediately with `confirmation_pending: true`, `success: true` and zeroed result fields, and a background task does the work. A background failure is only logged; the caller is never told.","default":true}}},"OrderPrepareWalletResponse":{"type":"object","description":"Response for `POST /exchanges/{exchange_id}/prepare-wallet`.","required":["gas_sponsored","allowances_set","confirmation_pending","success"],"properties":{"gas_sponsored":{"type":"boolean","description":"Always `false` when `confirmation_pending` is `true` — the work had not run yet."},"gas_tx_hash":{"type":"string","nullable":true},"gas_amount":{"type":"string","nullable":true,"description":"Present only when gas was actually sponsored."},"allowances_set":{"type":"integer","description":"Number of allowances set. Always `0` when `confirmation_pending` is `true`."},"confirmation_pending":{"type":"boolean","description":"`true` when the caller passed `wait_for_confirmation: false` and the preparation is still running in the background."},"success":{"type":"boolean","description":"Always `true` on a 200, INCLUDING the fire-and-forget path where nothing has been attempted yet. It is not evidence the wallet is ready."}}},"OrderPolymarketEnableTradingRequest":{"type":"object","required":["wallet_address"],"properties":{"wallet_address":{"type":"string","description":"The wallet to enable. Must be a wallet owned by the authenticated caller."}}},"OrderPolymarketEnableTradingResponse":{"type":"object","required":["success","message"],"properties":{"success":{"type":"boolean"},"message":{"type":"string"},"usdc_tx_hash":{"type":"string","description":"OMITTED entirely (not null) when no USDC approval was needed."},"ctf_tx_hash":{"type":"string","description":"OMITTED entirely (not null) when no CTF approval was needed."}}},"OrderPolymarketEnableImportedTradingRequest":{"type":"object","description":"Body for `POST /exchanges/polymarket/enable-imported-trading`. Neither field is trusted — both are checked against the authenticated caller before any credential is minted.","required":["wallet_address","turnkey_org_id"],"properties":{"wallet_address":{"type":"string","description":"The imported Polymarket EOA. Must already live in the caller's Turnkey sub-org."},"turnkey_org_id":{"type":"string","description":"Sub-org id holding the imported key. Must equal the caller's own org."}}},"OrderPolymarketEnableImportedTradingResponse":{"type":"object","required":["success","wallet_address"],"properties":{"success":{"type":"boolean"},"wallet_address":{"type":"string"}}},"OrderPredictfunEnableTradingRequest":{"type":"object","required":["wallet_address"],"properties":{"wallet_address":{"type":"string","description":"BSC EOA to enable. Must be owned by the authenticated caller."}}},"OrderPredictfunEnableTradingResponse":{"type":"object","required":["success","message","tx_hashes"],"properties":{"success":{"type":"boolean"},"message":{"type":"string"},"tx_hashes":{"type":"object","description":"Per-variant approval transaction hashes, keyed by variant label (e.g. `plain-binary`, `yield-negrisk`). A variant already approved is a no-op and contributes no entry.","additionalProperties":{"type":"string"}}}},"OrderOpinionEnableTradingRequest":{"type":"object","required":["wallet_address"],"properties":{"wallet_address":{"type":"string"}}},"OrderOpinionEnableTradingResponse":{"type":"object","required":["success","message"],"properties":{"success":{"type":"boolean"},"message":{"type":"string"},"usdt_tx_hash":{"type":"string","description":"OMITTED entirely (not null) when no USDT approval was needed."}}},"OrderKalshiEnableTradingRequest":{"type":"object","description":"Body for `POST /exchanges/kalshi/enable-trading`. Kalshi trades against the user's OWN Kalshi account, so the caller supplies their own Kalshi API credentials — Kairos does not provision them.","required":["api_key_id","private_key_pem"],"properties":{"api_key_id":{"type":"string","description":"Kalshi API Key ID (the public identifier). Must be non-blank."},"private_key_pem":{"type":"string","description":"The matching RSA private key, PEM-encoded. Both PKCS#1 (`BEGIN RSA PRIVATE KEY`) and PKCS#8 (`BEGIN PRIVATE KEY`) are accepted, and a key pasted as a single line with literal `\\n` escape sequences is normalized server-side."}}},"OrderKalshiEnableTradingResponse":{"type":"object","required":["success","message"],"properties":{"success":{"type":"boolean"},"message":{"type":"string"}}},"OrderPredictfunAccountReferral":{"type":"object","required":["status"],"properties":{"code":{"type":"string","nullable":true},"status":{"type":"string","description":"`LOCKED` or `UNLOCKED` per Predict.fun's spec. Kept as an open string so a new upstream state does not break deserialization — do not treat it as a closed enum."}}},"OrderPredictfunAccountPoints":{"type":"object","required":["total"],"properties":{"total":{"type":"number","format":"double"}}},"OrderPredictfunAccountResponse":{"type":"object","description":"Response for `GET /exchanges/predictfun/account` — the `data` block of Predict.fun's own `GET /v1/account`, forwarded verbatim. Upstream may add fields.","required":["name","address","referral","points"],"properties":{"name":{"type":"string"},"address":{"type":"string"},"image_url":{"type":"string","nullable":true},"referral":{"$ref":"#/components/schemas/OrderPredictfunAccountReferral"},"points":{"$ref":"#/components/schemas/OrderPredictfunAccountPoints"}}},"OrderCheckResolutionRequest":{"type":"object","required":["condition_id"],"properties":{"condition_id":{"type":"string","description":"Polymarket condition id. Two forms are accepted and routed differently upstream: a `0x…` hex condition id, or a numeric Gamma market id."},"token_id":{"type":"string","nullable":true,"description":"Optional outcome token id, to check a specific outcome's redeemability."}}},"OrderCheckResolutionResponse":{"type":"object","required":["condition_id","resolved","redeemable"],"properties":{"condition_id":{"type":"string"},"resolved":{"type":"boolean"},"winning_outcome":{"type":"integer","description":"Index of the winning outcome. OMITTED entirely (not null) when unresolved or unknown."},"resolution_time":{"type":"string","description":"OMITTED entirely (not null) when unavailable."},"redeemable":{"type":"boolean"}}},"OrderHyperliquidWithdrawPrepareRequest":{"type":"object","description":"Body for `POST /exchanges/hyperliquid/withdraw/prepare`. This call is PURE — it writes nothing and has no side effects; it only builds the typed data for you to sign.","required":["wallet_address","amount"],"properties":{"wallet_address":{"type":"string","description":"The Hyperliquid main wallet. Must be owned by the authenticated caller."},"amount":{"type":"string","description":"USDC amount as a plain decimal string. Only ASCII digits and a single `.` not in first position are accepted, and it must parse to a finite value > 0 — `1e5`, `.5`, `-1`, `inf` and `NaN` are all rejected. Kairos enforces NO minimum and NO maximum; Hyperliquid's own withdrawal minimum and flat fee are applied venue-side."},"destination":{"type":"string","nullable":true,"description":"Arbitrum destination address. **Defaults to `wallet_address`** (withdraw to self) when omitted. Kairos does NOT validate this address — see the endpoint description."}}},"OrderHyperliquidWithdrawPrepareResponse":{"type":"object","required":["typed_data","time","destination","amount"],"properties":{"typed_data":{"type":"object","description":"The EIP-712 typed data for Hyperliquid's `withdraw3` action. Sign this with the main wallet.","additionalProperties":true},"time":{"type":"integer","format":"int64","description":"Millisecond timestamp that is ALSO the action's nonce. Pass it back unchanged to `POST /exchanges/hyperliquid/withdraw`."},"destination":{"type":"string"},"amount":{"type":"string"}}},"OrderHyperliquidWithdrawRequest":{"type":"object","required":["wallet_address","amount","time","signature"],"properties":{"wallet_address":{"type":"string"},"amount":{"type":"string"},"time":{"type":"integer","format":"int64","description":"The `time` from prepare. Doubles as the nonce — Hyperliquid's replay protection is the only thing preventing a duplicate withdrawal."},"signature":{"type":"string","description":"0x-prefixed 65-byte signature over the prepared typed data, produced by the main wallet. Kairos never signs a withdrawal."},"destination":{"type":"string","nullable":true,"description":"Defaults to `wallet_address`. Must match what you signed, or Hyperliquid rejects it."}}},"OrderHyperliquidActionResponse":{"type":"object","description":"Response for `POST /exchanges/hyperliquid/withdraw` and `POST /exchanges/hyperliquid/transfer`.","required":["success","message"],"properties":{"success":{"type":"boolean"},"message":{"type":"string","description":"`Withdrawal submitted` or `Transfer submitted`. Submission only — not a confirmation of settlement."}}},"OrderHyperliquidTransferPrepareRequest":{"type":"object","description":"Body for `POST /exchanges/hyperliquid/transfer/prepare`. Pure — writes nothing.","required":["wallet_address","amount","to_perp"],"properties":{"wallet_address":{"type":"string"},"amount":{"type":"string","description":"USDC amount, same plain-decimal validation as withdraw. No Kairos-side minimum or maximum."},"to_perp":{"type":"boolean","description":"`true` moves spot → perp; `false` moves perp → spot. Funds never leave the account."}}},"OrderHyperliquidTransferPrepareResponse":{"type":"object","required":["typed_data","time","amount","to_perp"],"properties":{"typed_data":{"type":"object","description":"EIP-712 typed data for Hyperliquid's `usdClassTransfer` action.","additionalProperties":true},"time":{"type":"integer","format":"int64","description":"Millisecond timestamp, doubles as the nonce."},"amount":{"type":"string"},"to_perp":{"type":"boolean"}}},"OrderHyperliquidTransferRequest":{"type":"object","required":["wallet_address","amount","time","to_perp","signature"],"properties":{"wallet_address":{"type":"string"},"amount":{"type":"string"},"time":{"type":"integer","format":"int64"},"to_perp":{"type":"boolean"},"signature":{"type":"string","description":"0x-prefixed 65-byte signature from the MAIN wallet. Hyperliquid forbids agent wallets from class transfers, so a Kairos-held agent key cannot sign this."}}},"OrderDepositWalletIdentityRequest":{"type":"object","description":"The common identity body shared by the deposit-wallet endpoints. All three fields are checked against the authenticated caller and can only ever CONFIRM authority, never grant it — omitting them is not an option here, but supplying someone else's is a `403`.","required":["user_id","turnkey_org_id","owner_address"],"properties":{"user_id":{"type":"string","format":"uuid"},"turnkey_org_id":{"type":"string"},"owner_address":{"type":"string","description":"The owner EOA. The deposit-wallet address itself is resolved server-side from `UserIdentity`, never taken from the body."}}},"OrderDepositWalletOnboardResponse":{"type":"object","description":"Response for `POST /exchanges/polymarket/deposit-wallet/onboard`.","required":["deposit_wallet_address","batch_tx_id","success"],"properties":{"deposit_wallet_address":{"type":"string","description":"The deterministic (CREATE2) ERC-1967 proxy address for this owner."},"deploy_tx_id":{"type":"string","nullable":true,"description":"Relayer transaction id for the proxy deployment. `null` when the wallet was already deployed — the relayer is idempotent and that case is treated as success."},"batch_tx_id":{"type":"string","description":"Relayer transaction id for the confirmed trading-approval batch. Empty when no batch was submitted (approvals already present or optional approvals skipped)."},"success":{"type":"boolean"}}},"OrderDepositWalletBatchCall":{"type":"object","description":"One call inside a signed `Batch`.","required":["target","value","data"],"properties":{"target":{"type":"string","description":"0x contract address."},"value":{"type":"string","description":"Native value as a DECIMAL string — hex is rejected for any non-zero value. The allow-list requires this to be zero on every permitted batch shape."},"data":{"type":"string","description":"0x-prefixed calldata."}}},"OrderImportedRelayInfoRequest":{"type":"object","required":["user_id","turnkey_org_id","owner_address","sig_type"],"properties":{"user_id":{"type":"string","format":"uuid"},"turnkey_org_id":{"type":"string"},"owner_address":{"type":"string"},"sig_type":{"$ref":"#/components/schemas/OrderImportedSigType"}}},"OrderImportedSigType":{"type":"string","description":"Which imported Polymarket wallet model this is. Lower-case on the wire.","enum":["proxy","safe"]},"OrderImportedRelayInfoResponse":{"type":"object","required":["nonce"],"properties":{"nonce":{"type":"string","description":"Relayer nonce as a decimal U256 string."},"relay":{"type":"string","nullable":true,"description":"The GSN relay address, needed to build the PROXY digest. **Always `null` when `sig_type` is `safe`.**"}}},"OrderErrorActionType":{"type":"string","description":"Machine-readable recovery-action identifier, snake_case on the wire.","enum":["enable_trading","reenable_trading","update_policies","regenerate_api_key","contact_support","retry","review_order","adjust_price","adjust_size","browse_markets","refresh_market","create_new_order","add_funds","reduce_size","swap_usdc","add_matic","approve_usdc","approve_ctf","retry_approval","wait_and_retry","check_status","use_limit_order","update_kalshi_credentials","enable_bridge_funding"]},"OrderErrorAction":{"type":"object","description":"An actionable recovery step a client can surface to the end user.","required":["action","label","primary"],"properties":{"action":{"$ref":"#/components/schemas/OrderErrorActionType"},"label":{"type":"string","description":"Human-readable button/link label.","example":"Add Funds"},"url":{"type":"string","nullable":true},"primary":{"type":"boolean","description":"Whether this is the primary/recommended action."}}},"OrderFailure":{"type":"object","description":"Structured failure detail attached to a `failed` order (`GET /orders`,\n`GET /orders/{order_id}`, and the `order_update` WebSocket event). OMITTED\nentirely (not `null`) when the order has no failure.\n\n`classification` is the **authoritative retry policy**:\n`expected_user_rejection` is a well-formed request the venue or the user's own\ninputs rejected — do not retry without changing the order; `retryable` is a\ntransient condition that may succeed on retry; `non_retryable` cannot succeed\nby retrying the same order.\n\n`details.actions` is a **UI affordance only**. It is derived from `code`\nindependently of `classification` and may include `retry` (even as `primary`)\non a `non_retryable` failure, because the suggested buttons target an end user\nwho may be able to change something first. Do NOT build an automatic retry\nloop from `actions`; branch on `classification`. A client that is not rendering\nbuttons can ignore `actions` entirely.","required":["code","classification","details"],"properties":{"code":{"type":"string","description":"Machine-readable failure code. This is NOT the same namespace as\n`details.code`: `code` is the execution error's wire code (e.g.\n`FOK_NOT_FILLED`), while `details.code` is the `OrderErrorCode`\nused for incident grouping (e.g. `MARKET_FOK_NOT_FILLED`). For a\nvenue `ExchangeError` the two coincide.","example":"FOK_NOT_FILLED"},"classification":{"type":"string","enum":["expected_user_rejection","retryable","non_retryable"],"description":"Authoritative retry policy for this failure."},"details":{"$ref":"#/components/schemas/OrderErrorDetails"}}},"OrderErrorCode":{"type":"string","description":"Machine-readable error code, SCREAMING_SNAKE_CASE on the wire.","enum":["AUTH_CREDENTIALS_NOT_FOUND","AUTH_CREDENTIALS_INVALID","AUTH_POLICY_OUTDATED","AUTH_INSUFFICIENT_SCOPE","AUTH_POLYMARKET_API_KEY_INVALID","AUTH_TURNKEY_WALLET_NOT_FOUND","AUTH_TURNKEY_AUTH_FAILED","AUTH_TURNKEY_SIGNING_FAILED","VALIDATION_INVALID_ORDER","VALIDATION_INVALID_PRICE","VALIDATION_INVALID_SIZE","VALIDATION_MARKET_NOT_FOUND","VALIDATION_TOKEN_NOT_FOUND","VALIDATION_ORDER_EXPIRED","VALIDATION_MARKET_NOT_SETTLED_ON_CHAIN","AUTH_IDENTITY_MISMATCH","FUNDS_INSUFFICIENT_USDC","FUNDS_INSUFFICIENT_GAS","FUNDS_INSUFFICIENT_TOTAL","FUNDS_INSUFFICIENT_BALANCE","FUNDS_COLLATERAL_LOCATION","ALLOWANCE_USDC_NOT_SET","ALLOWANCE_CTF_NOT_SET","ALLOWANCE_APPROVAL_FAILED","NETWORK_RPC_ERROR","NETWORK_TIMEOUT","NETWORK_HTTP_ERROR","NETWORK_ERROR","EXCHANGE_POLYMARKET_API_ERROR","EXCHANGE_POLYMARKET_UNAUTHORIZED","EXCHANGE_POLYMARKET_MARKET_CLOSED","EXCHANGE_POLYMARKET_RATE_LIMITED","EXCHANGE_KALSHI_API_ERROR","EXCHANGE_KALSHI_UNAUTHORIZED","EXCHANGE_KALSHI_MARKET_CLOSED","EXCHANGE_ERROR","EXCHANGE_UNSUPPORTED","SIGNATURE_EIP712_FAILED","SIGNATURE_ERROR","GAS_ESTIMATION_FAILED","TRANSACTION_FAILED","GAS_SPONSORSHIP_FAILED","TRANSACTION_TIMEOUT","MARKET_NOT_READY","MARKET_PAUSED","MARKET_INSUFFICIENT_LIQUIDITY","ORDERBOOK_UNAVAILABLE","MARKET_FOK_NOT_FILLED","MARKET_ORDER_SIZE_EXCEEDS_MAX","DATABASE_ERROR","DATABASE_CREDENTIAL_DECRYPTION_FAILED","INTERNAL_ERROR"],"example":"FUNDS_INSUFFICIENT_USDC"},"OrderErrorDetails":{"type":"object","description":"Structured, machine-readable error payload shared across order-execution error responses.","required":["code","message","actions"],"properties":{"code":{"$ref":"#/components/schemas/OrderErrorCode"},"message":{"type":"string","description":"Human-readable error message."},"details":{"type":"string","nullable":true,"description":"Optional extended explanation."},"metadata":{"nullable":true,"description":"Error-type-specific structured metadata (shape varies by `code` — e.g. `{type: \"insufficient_funds\", required, available, currency, shortfall}` or `{type: \"rate_limit\", retry_after_seconds, limit, window}`). Untagged union; treat as opaque unless you match on the embedded `type` field.","additionalProperties":true},"actions":{"type":"array","description":"Zero or more actionable recovery steps a client can surface.","items":{"$ref":"#/components/schemas/OrderErrorAction"}}}},"OrderErrorResponse":{"type":"object","description":"Standard structured error body returned by every endpoint that surfaces an `ExecutionError`/`ApiError` (order submission, cancel-all, and the other endpoints noted as returning this schema). Endpoints noted as \"empty body (status code only)\" do NOT use this shape.","required":["error","error_details"],"properties":{"error":{"type":"string","description":"Same text as `error_details.message`, kept for backwards-compatible consumers."},"code":{"type":"string","nullable":true,"description":"Debug-formatted copy of `error_details.code` (e.g. `\"FundsInsufficientUsdc\"` — PascalCase, NOT the SCREAMING_SNAKE_CASE wire code in `error_details.code`)."},"error_details":{"$ref":"#/components/schemas/OrderErrorDetails"}}},"OrderSimpleErrorResponse":{"type":"object","description":"Minimal error body (`{\"error\": \"...\"}`, optionally `{\"code\": \"...\"}`) used by the auth middleware (401/403/429 on any endpoint), the external-signing `/v2/orders/*` lane, and the Kalshi-offchain balance endpoint. Distinct from `OrderErrorResponse` — these endpoints do not emit the full structured `error_details` shape.","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable error message.","example":"Insufficient scope"},"code":{"type":"string","nullable":true,"description":"Present only on the Kalshi-offchain endpoints (omitted entirely, not null, when absent). Observed values: `PLATFORM_API_ACCESS_DENIED`, `PLATFORM_API_ACCESS_DISABLED`, `INSUFFICIENT_SCOPE`, `NOT_CONFIGURED`, `NO_CREDENTIALS`, `INVALID_USER_ID`, `MISSING_API_KEY_ID`, `INVALID_PRIVATE_KEY`, `INVALID_CREDENTIALS`, `CONFIG_ERROR`, `ENCRYPTION_ERROR`, `DB_ERROR`, `INTERNAL_ERROR`, `KALSHI_API_ERROR`."}}},"OrderCtfSplitMergeRequest":{"type":"object","required":["condition_id","amount"],"properties":{"condition_id":{"type":"string","description":"CTF conditionId of the market (0x…).","example":"0x1234abcd"},"market_id":{"type":["string","null"],"description":"OPTIONAL. Only enriches the synthetic BUY/SELL trade-log legs with the market's YES/NO token ids — the on-chain split/merge itself needs only `condition_id` + `amount`. When omitted the market context is resolved from `condition_id`; if it cannot be resolved either way the on-chain action still succeeds and only the best-effort trade-log enrichment is skipped.","example":"570362"},"amount":{"type":"string","description":"Decimal string. Units of collateral (split) or complete sets (merge). Must be > 0. An unquoted JSON number is also accepted, but it round-trips through a binary float — send the string form to preserve every digit.","example":"100"},"user_id":{"type":["string","null"],"description":"Optional. Must match the authenticated caller when present — identity is resolved server-side and these fields can never widen scope to another user."},"turnkey_org_id":{"type":["string","null"],"description":"Optional. Must match the authenticated caller when present."},"wallet_address":{"type":["string","null"],"description":"Optional wallet override; honored only if the wallet is owned by the caller. Omit to let the server resolve the signer wallet."}}},"OrderCtfSplitResponse":{"type":"object","required":["amount","condition_id","action","success"],"properties":{"tx_hash":{"type":["string","null"],"description":"On-chain transaction hash. `null` when the action completed without producing one — always check `success`.","example":"0xabc123"},"amount":{"type":"string","description":"Decimal string — `rust_decimal` always serializes as a string.","example":"100"},"condition_id":{"type":"string","example":"0x1234abcd"},"action":{"type":"string","enum":["split"]},"success":{"type":"boolean"}}},"OrderCtfMergeResponse":{"type":"object","required":["amount","condition_id","action","success"],"properties":{"tx_hash":{"type":["string","null"],"description":"On-chain transaction hash. `null` when the action completed without producing one — always check `success`.","example":"0xabc123"},"amount":{"type":"string","description":"Decimal string — `rust_decimal` always serializes as a string.","example":"290"},"condition_id":{"type":"string","example":"0x1234abcd"},"action":{"type":"string","enum":["merge"]},"success":{"type":"boolean"}}},"OrderCtfRedeemRequest":{"type":"object","required":["condition_id"],"properties":{"condition_id":{"type":"string","description":"CTF conditionId of the RESOLVED market.","example":"0x1234abcd"},"position_id":{"type":["string","null"],"description":"Position row to mark redeemed after success."},"market_id":{"type":["string","null"],"description":"Locates the position when position_id is omitted."},"token_id":{"type":["string","null"],"description":"Locates the position when position_id is omitted."},"user_id":{"type":["string","null"],"description":"Optional. Must match the authenticated caller when present — identity is resolved server-side and these fields can never widen scope to another user."},"turnkey_org_id":{"type":["string","null"],"description":"Optional. Must match the authenticated caller when present."},"wallet_address":{"type":["string","null"],"description":"Optional wallet override; honored only if the wallet is owned by the caller. Omit to let the server resolve the signer wallet."}}},"OrderCtfRedeemResponse":{"type":"object","required":["amount_redeemed","success"],"properties":{"tx_hash":{"type":["string","null"],"description":"On-chain transaction hash. `null` when no transaction was produced — always check `success`.","example":"0xabc123"},"amount_redeemed":{"type":"string","description":"Decimal string — `rust_decimal` always serializes as a string.","example":"100"},"success":{"type":"boolean"},"db_update_failed":{"type":"boolean","description":"OMITTED entirely when false (`skip_serializing_if`). Present and `true` only when the on-chain redeem succeeded but the bookkeeping write failed after retries — funds are safe, retry to fix bookkeeping."}}},"SyntheticBookLeg":{"type":"object","required":["venue","contract_id","weight"],"properties":{"venue":{"type":"string","example":"polymarket"},"contract_id":{"type":"string","description":"Provider market/contract identifier used by the Kairos market-data stream."},"token_id":{"type":"string","description":"Provider outcome-token identifier. Required except for merged-book venues that accept outcome_index."},"outcome_index":{"type":"integer","minimum":0,"description":"Outcome index for a merged-book venue when token_id is omitted."},"weight":{"type":"string","pattern":"^-?[0-9]+(?:\\.[0-9]{1,9})?$","example":"-1","description":"Signed decimal quantity weight, encoded as a string with at most nine decimals."}}},"SyntheticBookDefinitionRequest":{"type":"object","required":["legs","output_mode","depth"],"properties":{"legs":{"type":"array","minItems":1,"maxItems":15,"items":{"$ref":"#/components/schemas/SyntheticBookLeg"}},"output_mode":{"type":"string","enum":["bbo","aggregated_l2","decomposed_l2"]},"depth":{"type":"integer","minimum":1,"maximum":50,"example":50},"classification":{"type":"string","default":"arbitrary_basket","enum":["arbitrary_basket","relative_value","exact_equivalence","complement","partition","implication","range","guaranteed_payout"],"description":"Reviewed economic metadata; Kairos does not infer it from the formula."}}},"SyntheticBookCreateResponse":{"type":"object","required":["synthetic_id","subscription_id","created","fees_updated","sources_subscribed","expires_at_ms","ttl_ms"],"properties":{"synthetic_id":{"type":"string","pattern":"^syn_[0-9a-f]{16}$","example":"syn_0123456789abcdef"},"subscription_id":{"type":"string","format":"uuid","description":"Opaque account-owned public lease id. This is not the materializer's private numeric handle."},"created":{"type":"boolean","description":"True when this request created the canonical materialization; false when it joined an equivalent one."},"fees_updated":{"type":"boolean"},"sources_subscribed":{"type":"integer","minimum":0},"expires_at_ms":{"type":"integer","format":"int64"},"ttl_ms":{"type":"integer","format":"int64","example":600000}}},"SyntheticBookRefreshResponse":{"type":"object","required":["refreshed","subscription_id","synthetic_id","expires_at_ms","ttl_ms"],"properties":{"refreshed":{"type":"boolean","const":true},"subscription_id":{"type":"string","format":"uuid"},"synthetic_id":{"type":"string","pattern":"^syn_[0-9a-f]{16}$"},"expires_at_ms":{"type":"integer","format":"int64"},"ttl_ms":{"type":"integer","format":"int64","example":600000}}},"SyntheticBookReleaseResponse":{"type":"object","required":["released","subscription_id"],"properties":{"released":{"type":"boolean","const":true},"subscription_id":{"type":"string","format":"uuid"}}},"SyntheticBookErrorResponse":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","example":"synthetic_books_unavailable"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":true,"description":"Private materializer validation details; present only for rejected definitions.","nullable":true}}}}},"paths":{"/v1/synthetics":{"post":{"operationId":"createSyntheticBook","summary":"Create or join a Synthetic Book definition","tags":["Synthetic Books"],"description":"Creates an account-owned ten-minute lease for a canonical weighted book and returns the `synthetic_id` to subscribe to at `wss://stream.kairos.trade` with provider/topic `synthetic`.\n\nUses the caller's existing Kairos credential triple; no separate Synthetic Books credential, scope, or account allowlist is required. The full API-key triple also satisfies the mutation gate automatically.\n\nEquivalent canonical definitions share a data-plane materialization but receive distinct account-owned lease UUIDs. Maximum 15 legs, depth 50, 25 active leases per user, and 30 create requests/minute/user by default.\n","x-kairos-auth":"API key triple, or first-party session JWT plus X-Csrf-Token","x-kairos-rate-limit":"120 total requests/minute/user, 30 creates/minute/user, and 25 active leases/user by default","x-kairos-scope":"none","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookDefinitionRequest"}}}},"responses":{"200":{"description":"Lease created and canonical synthetic id returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookCreateResponse"}}}},"400":{"description":"Invalid formula, leg, mode, classification, or depth.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookErrorResponse"}}}},"401":{"description":"Missing or invalid account credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"403":{"description":"Invalid mutation credentials (including an invalid API-key triple).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"409":{"description":"The account already has 25 active leases.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookErrorResponse"}}}},"413":{"description":"Request body exceeds 64 KiB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookErrorResponse"}}}},"415":{"description":"Request body is not JSON.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookErrorResponse"}}}},"429":{"description":"Creation rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookErrorResponse"}}}},"502":{"description":"Private materializer unavailable or returned an invalid response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookErrorResponse"}}}},"503":{"description":"Synthetic Books is not configured on this execution node.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookErrorResponse"}}}}}}},"/v1/synthetics/subscriptions/{subscription_id}/refresh":{"post":{"operationId":"refreshSyntheticBookSubscription","summary":"Refresh a Synthetic Book lease","tags":["Synthetic Books"],"description":"Refreshes an unexpired lease owned by the authenticated user. Refresh every five minutes; a lease expires after ten minutes and an expired lease must be recreated.","x-kairos-auth":"API key triple, or first-party session JWT plus X-Csrf-Token","x-kairos-rate-limit":"120 requests/minute/user by default","x-kairos-scope":"none","parameters":[{"name":"subscription_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Lease refreshed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookRefreshResponse"}}}},"401":{"description":"Missing or invalid account credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"403":{"description":"Invalid mutation credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"404":{"description":"Lease does not exist or is owned by another account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookErrorResponse"}}}},"410":{"description":"Lease expired in the control plane or materializer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookErrorResponse"}}}},"429":{"description":"Synthetic Books request rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookErrorResponse"}}}},"502":{"description":"Private materializer unavailable or returned an invalid response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookErrorResponse"}}}},"503":{"description":"Synthetic Books is not configured on this execution node.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookErrorResponse"}}}}}}},"/v1/synthetics/subscriptions/{subscription_id}":{"delete":{"operationId":"releaseSyntheticBookSubscription","summary":"Release a Synthetic Book lease","tags":["Synthetic Books"],"description":"Releases an account-owned lease. The canonical materialization remains live while any other lease references it and otherwise enters its reclamation TTL.","x-kairos-auth":"API key triple, or first-party session JWT plus X-Csrf-Token","x-kairos-rate-limit":"120 requests/minute/user by default","x-kairos-scope":"none","parameters":[{"name":"subscription_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Lease released (also returned when the private handle was already gone).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookReleaseResponse"}}}},"401":{"description":"Missing or invalid account credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"403":{"description":"Invalid mutation credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"404":{"description":"Lease does not exist or is owned by another account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookErrorResponse"}}}},"429":{"description":"Synthetic Books request rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookErrorResponse"}}}},"502":{"description":"Private materializer unavailable or returned an invalid response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookErrorResponse"}}}},"503":{"description":"Synthetic Books is not configured on this execution node.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookErrorResponse"}}}}}}},"/v1/synthetics/{synthetic_id}":{"get":{"operationId":"getSyntheticBookDefinition","summary":"Inspect a Synthetic Book definition","tags":["Synthetic Books"],"description":"Returns materialization metadata only when the authenticated user owns an active lease for this canonical id. Cross-account subscriber counts are intentionally omitted.","x-kairos-auth":"API key triple or first-party session JWT","x-kairos-rate-limit":"120 requests/minute/user by default","x-kairos-scope":"none","parameters":[{"name":"synthetic_id","in":"path","required":true,"schema":{"type":"string","pattern":"^syn_[0-9a-f]{16}$"}}],"responses":{"200":{"description":"Current materialization metadata and canonical definition echo.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"synthetic_id is not canonical.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookErrorResponse"}}}},"401":{"description":"Missing or invalid account credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"404":{"description":"No active account-owned lease or unknown materialization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookErrorResponse"}}}},"429":{"description":"Synthetic Books request rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookErrorResponse"}}}},"502":{"description":"Private materializer unavailable or returned an invalid response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookErrorResponse"}}}},"503":{"description":"Synthetic Books is not configured on this execution node.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyntheticBookErrorResponse"}}}}}}},"/orders":{"post":{"operationId":"submitOrder","summary":"Submit a new order","description":"Submits a custodial order: Kairos signs and routes it to the venue on the caller's behalf (custodial signing). This is the standard order-entry path used by both the web app and API-key consumers who have not been allow-listed onto the self-custody \"external-signing\" lane (`POST /v2/orders/intent` + `POST /v2/orders/submit`).\n**Ownership.** The order is always created for the authenticated caller; any `user_id` in the body is accepted for backwards compatibility but is ignored — the JWT/API-key identity wins.\n**Ack semantics (async).** A 200 response means the order was validated, persisted, and enqueued — it does NOT mean the order is live on the venue yet. The response `status` is `\"queued\"` (or, on an idempotent replay, the current status of the previously-created order). Track the order via `GET /orders/{order_id}`, `GET /orders` polling, or (lowest latency) the `/ws` socket, which pushes `order_update` / `fill` events as the worker submits to the venue and fills arrive.\n**Idempotency.** Pass `client_order_id` to make retries safe: a second submit with the same `(user, exchange_id, market_id, client_order_id)` tuple returns the existing order instead of creating a duplicate, and does **not** consume a rate-limit slot. If omitted, the server generates a random one (i.e. retries without a client-supplied id are NOT deduped).\n**Provider routing.** `exchange_id` selects the venue-specific executor (`polymarket`, `kalshi`, `predictfun`, `opinion`, `hyperliquid`, …; `kalshi_offchain` is a deprecated alias of `kalshi`). Each venue advertises its own capabilities (supported `time_in_force` values, max price, order types) — a request that is well-formed in general but unsupported by the specific venue is rejected with `400 EXCHANGE_UNSUPPORTED` / `400 VALIDATION_INVALID_ORDER`.\n**Hyperliquid HIP-4.** Use the numeric outcome id as `market_id`, the selected display label as `outcome`, and the side coin from the market-data `OrderbookSnapshot.token_ids` as `token_id` (for example `#1010` or `#1011`). Quantities are whole shares. Hyperliquid maps `GTC`/`GTD` to venue GTC and `IOC`/`FAK`/`FOK` to venue IOC. Single-order and selected-order batch cancellation are supported; cancel-all and fee quotes are unavailable.\n**Order types.** `kind=\"market\"` is a marketable taker order — pair it with `time_in_force` `FOK` (fill-or-kill) or `FAK`/`IOC` (fill-and-kill / immediate-or-cancel, partials allowed, remainder cancelled). `kind=\"limit\"` is a resting quote — pair it with `GTC` (good-til-cancelled) or `GTD` (good-til-date, requires `expiration_minutes`). `price` is REQUIRED for every order, including market orders — for a marketable order it is the limit you are willing to cross to, not a market-price sentinel.\n**Circuit breakers & gating.** Before persisting, the order passes through, in order: the per-user order rate limit, size/price bounds, the venue time-in-force capability gate, a global + per-exchange execution kill-switch, the custodial trading-enabled check, the post-only capability gate, side/kind/TIF-specific breakers, and the API-key minimum-notional rule. Kill-switch and restriction rejections are `503` with `error_details.code = MARKET_PAUSED`; a disabled or identity-less account is `403` with `AUTH_CREDENTIALS_INVALID`. Designated canary users bypass both breaker checks.\n**Rate limiting.** A Redis sliding-window limiter caps order submissions per authenticated user (default 5 orders per second, `ORDER_RATE_LIMIT_PER_SEC`). API keys with an `orders` override are checked against BOTH their own per-credential window and the aggregate per-user window (either denying is a `429`); idempotent replays are resolved before the limiter and never consume a slot. The limiter fails closed — an unreachable Redis denies with `429`. The `429` body carries `error_details.code = VALIDATION_INVALID_ORDER` with the message `Order rate limit exceeded` (there is no dedicated rate-limit code on this path), and no `Retry-After` header.\n**Auth & scope.** Requires `trade:execute`. This mutation endpoint is additionally gated by a service-token/CSRF/API-key check (`RequireServiceToken`), which any request bearing `X-Client-Id` / `X-Api-Key` / `X-Api-Secret` satisfies automatically — no extra header is needed for API-key consumers.","tags":["Orders"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSubmitRequest"},"examples":{"hyperliquid":{"summary":"Hyperliquid HIP-4 limit buy on side 1","value":{"exchange_id":"hyperliquid","market_id":"101","token_id":"#1011","outcome":"No","side":"buy","kind":"limit","quantity":25,"price":0.42,"time_in_force":"GTC"}}}}}},"responses":{"200":{"description":"Order accepted, persisted, and enqueued for execution. Does not imply the order is live on the venue yet — poll or subscribe to `/ws` for the terminal state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSubmitResponse"}}}},"400":{"description":"Validation or admission failure. `error_details.code` distinguishes them:\n- `VALIDATION_INVALID_ORDER` — unparseable `side`/`kind`, `time_in_force` present but unrecognized (never silently downgraded to `GTC`), `expiration_minutes` outside `[1, 43200]` for a GTD order, `max_slippage` outside `[0, 0.5]`, `max_slippage_cents` outside `[1, 99]`, `max_retries` > 20, a TIF or `post_only` combination the venue's capabilities do not advertise, an unrecognized `collateral` mode, `collateral=fund` without `max_bridge_fee_usdc` or `max_funding_wait_ms` (the message names the missing cap), either cap sent with a mode other than `fund`, an API-key BUY under the `$5` minimum notional.\n- `VALIDATION_INVALID_SIZE` — quantity not positive, below `MIN_ORDER_QUANTITY` (BUY only), or above `1,000,000`.\n- `VALIDATION_INVALID_PRICE` — `price` missing (it is required for EVERY order, including market orders), not positive, or above the venue's max price; same bounds for `trigger_price`.\n- `EXCHANGE_UNSUPPORTED` — `exchange_id` is not a registered exchange.\n- `FUNDS_INSUFFICIENT_USDC` / `FUNDS_INSUFFICIENT_BALANCE` — a pre-trade balance check or a venue balance rejection. NOTE: an insufficient-balance reject is a `400`, not a `502`.\n- `MARKET_FOK_NOT_FILLED` — a FOK/marketable order could not be filled, or realized slippage exceeded `max_slippage_cents`.\n- `EXCHANGE_POLYMARKET_MARKET_CLOSED` — the market is closed / trading halted.\n- `ALLOWANCE_CTF_NOT_SET` / `ALLOWANCE_USDC_NOT_SET` — a missing on-chain approval, classified from the venue's rejection text.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Missing `trade:execute` scope (`API key missing trade:execute scope`), API-key access to this provider disabled (`API-key access to <provider> is disabled`), or trading not enabled / no Turnkey identity for the user's custodial signing identity (`AUTH_CREDENTIALS_INVALID`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"404":{"description":"`VALIDATION_MARKET_NOT_FOUND` — the market/contract could not be resolved on the venue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"422":{"description":"`ORDERBOOK_UNAVAILABLE` — a market order could not be priced because the live orderbook is missing or went stale between pricing and submission. Retryable by the caller (a fresh book may arrive); never auto-retried server-side within the same attempt.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"429":{"description":"Per-user (and, for keys with an `orders` override, per-credential) order-submission rate limit exceeded, or the limiter's Redis was unreachable (fails closed). Body carries `error_details.code = VALIDATION_INVALID_ORDER`, message `Order rate limit exceeded`. A venue-side rate limit instead surfaces as `EXCHANGE_POLYMARKET_RATE_LIMITED`. No `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"`INTERNAL_ERROR` (capabilities unavailable, trading-status read failed, idempotency service unavailable, pre-trade preparation failed), `DATABASE_ERROR`, or `SIGNATURE_ERROR` (custodial signing failed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"502":{"description":"The venue call failed — a network/RPC error (`NETWORK_ERROR`) or an unclassified exchange rejection (`EXCHANGE_ERROR`). Balance, allowance and market-closed rejections are classified out of this bucket into `400`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"503":{"description":"Execution disabled by a circuit breaker (global or per-exchange halt, side/kind/TIF restriction) — `MARKET_PAUSED`; or an execution lock could not be acquired (`INTERNAL_ERROR`, \"System is busy\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"504":{"description":"`NETWORK_TIMEOUT` — the venue call timed out. The order may still have reached the venue; verify with `GET /orders/{order_id}` before resubmitting.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}},"get":{"operationId":"listOrders","summary":"List the authenticated user's orders","description":"Returns orders belonging to the authenticated caller, most recent first, with optional status filtering and offset pagination. When `status` is omitted, every status (including terminal ones) is returned.\n**Response is trimmed.** To keep this endpoint cheap for polling UIs, the heavy `raw` (full venue response, can be ~100 KB/order) and `metadata` fields are stripped from every row (`raw: null`, `metadata: null`); the `outcome` label is preserved by falling back to `metadata.outcome` before stripping. Use `GET /orders/{order_id}` for the full row including `raw`.\n**Auth & scope.** Requires `trade:read`. For API-key callers without an `exchange_id` filter, every distinct provider the user has orders on is checked for API-key access before the list is returned.","tags":["Orders"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:read","parameters":[{"name":"exchange_id","in":"query","required":false,"description":"Filter to a single venue (e.g. `polymarket`, `kalshi`, `hyperliquid`).","schema":{"type":"string","example":"polymarket"}},{"name":"status","in":"query","required":false,"description":"A single status or a comma-separated set, e.g. `filled` or `open,pending,live,partial`. Omit to return all statuses. Matched against the RAW stored status strings, which are not identical to the `OrderStatus` enum: `queued`/`locked`/`executing`/`orphaned` are all stored as `pending`, and the stored set additionally includes `open`, `processing` and `delayed`.","schema":{"type":"string","example":"live,partial"}},{"name":"active_only","in":"query","required":false,"description":"When `true`, return only working orders — stored status in `pending`, `queued`, `locked`, `processing`, `executing`, `live`, `open`, `partial` or `delayed`, excluding a `partial` market/FAK/IOC/FOK order (which is terminal in practice).","schema":{"type":"boolean","default":false}},{"name":"limit","in":"query","required":false,"description":"Maximum rows to return. Clamped to at most 500 server-side. There is no lower clamp, so a zero or negative value is passed through to the query — send a positive value.","schema":{"type":"integer","format":"int64","default":50,"maximum":500,"example":50}},{"name":"offset","in":"query","required":false,"description":"Rows to skip, for paginating order history. Clamped to >= 0. **Ignored when `before_id` is set** — the two pagination modes are mutually exclusive.","schema":{"type":"integer","format":"int64","default":0,"minimum":0,"example":0}},{"name":"before_id","in":"query","required":false,"description":"Keyset cursor on `(submitted_at, id)` — return rows strictly older than this order. Preferred over `offset` for deep pagination; takes precedence over it.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The caller's orders (raw/metadata stripped), newest first.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Order"}}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Missing `trade:read` scope, or API-key access to one of the returned providers is disabled. Empty body (status code only)."},"500":{"description":"Database read failed. Empty body (status code only)."},"503":{"description":"For an API-key caller with no `exchange_id` filter, the lookup of which providers the user has orders on failed, so per-provider access could not be checked (fails closed). Empty body (status code only)."}}}},"/orders/{order_id}":{"get":{"operationId":"getOrder","summary":"Get a single order by internal id","description":"Returns the full order row (including `raw`, the unredacted venue response, and `metadata`) for a single order the caller owns. Use this after `POST /orders` to poll a specific order's terminal state, or after `GET /orders` (which strips `raw`/`metadata`) to drill into one row.","tags":["Orders"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:read","parameters":[{"name":"order_id","in":"path","required":true,"description":"Internal Kairos order id (the `order_id` returned by `POST /orders` or `POST /v2/orders/submit`).","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The order, including `raw` and `metadata`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"400":{"description":"`order_id` is not a valid UUID (framework-level path rejection — plain-text body, not JSON)."},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Order belongs to another user, missing `trade:read` scope, or API-key access to the order's provider is disabled. Empty body (status code only)."},"404":{"description":"No order with that id. Empty body (status code only)."},"500":{"description":"Database read failed. Empty body (status code only)."},"503":{"description":"API-key access to the order's provider could not be checked (access-cache read failed — fails closed). Empty body (status code only)."}}}},"/orders/{order_id}/cancel":{"post":{"operationId":"cancelOrder","summary":"Cancel a single order","description":"Cancels one order by internal id. Polymarket cancellation is an L2 API-key (HMAC) operation on the venue side, so no EOA/wallet signature is required — this works identically for custodial and external-signing orders.\n**This endpoint almost always returns `200`, even on failure to cancel.** `success: false` with a `message` covers: the order is already terminal (filled/cancelled/expired/failed), or the venue currently refuses the cancel (still live — retry). Non-2xx status codes are reserved for auth/ownership/not-found/infrastructure failures, not \"the cancel didn't happen.\"\n**Cancel-before-submit race.** If the order hasn't reached the exchange yet (still `pending`/`queued`), it is cancelled locally via an atomic conditional update that races safely against the in-flight submission. If the submission wins that race (order mid-submission), the response is `success: false` asking the caller to retry shortly.\n**Fill-during-cancel.** A venue acknowledgement is followed by terminal fill verification. Until the response carries `terminal_fill_verification.state: complete`, it returns `success: false` and consumers must keep protection active. The completed receipt's `final_filled_quantity` is the authoritative venue cumulative (never the latest fill delta), including explicit zero, and is published only after the corresponding Trade/Position fold. Kalshi may complete this barrier synchronously; other venues normally complete through the polling worker and expose the retained receipt on retry or `GET /orders/{order_id}`.\n**Auth & scope.** Requires `trade:execute` and ownership of the order (`403` otherwise).","tags":["Orders"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","parameters":[{"name":"order_id","in":"path","required":true,"description":"Internal Kairos order id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Cancel outcome. Check `success` — a `200` does not guarantee the order was actually cancelled (see description).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderCancelResponse"}}}},"400":{"description":"No executor is registered for the order's exchange. Empty body (status code only)."},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Order belongs to another user, missing `trade:execute` scope, or API-key access to the order's provider is disabled. Note this endpoint is behind the service-token/CSRF/API-key mutation gate, whose own rejections are `403` with a minimal `error` body. Otherwise empty body (status code only)."},"404":{"description":"No order with that id, or the order disappeared mid-cancel. Empty body (status code only)."},"500":{"description":"Database read/write failed, credentials could not be loaded, or the cancel outcome could not be confirmed at all. Empty body (status code only)."},"503":{"description":"API-key access to the order's provider could not be checked (fails closed). Empty body (status code only)."}}}},"/orders/{order_id}/amend":{"post":{"operationId":"amendOrder","summary":"Reprice a resting limit order in place","description":"Moves a resting limit order's price in one venue round trip. The order keeps its Kairos `order_id` AND its venue `exchange_order_id` — that is the property this endpoint exists to provide: no supersession to record, and no second order whose fill could be booked twice. The cancel-and-replace it replaces leaves the book empty of the order for the whole cancel round trip and mints a second id to reconcile.\n**Venue support.** Only venues advertising `supports_native_amend` in `GET /exchanges/{exchange_id}/capabilities` — today `kalshi` alone. Anything else is `409` `EXCHANGE_AMEND_UNSUPPORTED`, answered before the order's own state is inspected so the verdict is a permanent property of the venue rather than of this moment. There is deliberately NO internal cancel-and-replace fallback: that substitution changes queue position, order identity and the caller's failure modes, so it is the caller's decision.\n**Reprice only.** `quantity` is accepted solely to be refused with `409` `EXCHANGE_AMEND_QUANTITY_UNSUPPORTED`, before the order is read — refused rather than silently ignored so a caller never believes it resized. Limit orders only; `price` must be strictly between 0 and 1, in the order's own outcome's terms.\n**This endpoint almost always returns `200`.** `success: false` is an ambiguous venue outcome, never a transport failure — the same convention `POST /orders/{order_id}/cancel` uses, because a 5xx invites a retry against an order that may already have been amended. It covers a terminal order, an order not yet at the venue, and a venue that refused; on every one of those the order is untouched and still resting on its old price. Branch on `success`, not on the status code.\n**Queue position.** Kalshi preserves it only when an amend DECREASES size; a reprice forfeits it — but so does the cancel/replace this replaces, so nothing is lost. The win is the closed off-book window, not priority.\n**Admission.** Runs the same checks a submit does — kill switch, side/kind/TIF circuit breakers evaluated against the order's own side and kind (an amend changes neither), and the order rate limit. An immediate fill from a crossing amend is booked through the normal poller path, not from this response.\n**Auth & scope.** Requires `trade:execute` and ownership of the order.","tags":["Orders"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","parameters":[{"name":"order_id","in":"path","required":true,"description":"Internal Kairos order id.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderAmendRequest"}}}},"responses":{"200":{"description":"Amend outcome. Check `success` — a `200` does not guarantee the order was actually amended (see description).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderAmendResponse"}}}},"400":{"description":"The order is not a limit order, the requested price is not strictly between 0 and 1, or the price is off the market's tick grid — checked before any venue request, against the same grid a submit is held to, with the message naming the tick (`VALIDATION_INVALID_ORDER` / `VALIDATION_INVALID_PRICE`). A non-UUID `{order_id}` or a missing/unparseable JSON body is rejected before the handler runs and answers `400` with a plain-text body rather than the `OrderErrorResponse` envelope. Both representations are modelled below; branch on the response `Content-Type` and do not assume `error_details` is present on every `400` from this path.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}},"text/plain":{"schema":{"type":"string","description":"Rejection emitted before the handler runs — a malformed `{order_id}` or a missing/unparseable JSON body. Carries no `error_details`; the text is not a stable contract, so branch on the status and media type, never on its wording.","example":"Invalid URL: Cannot parse `order_id` with value `not-a-uuid` to a `Uuid`"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Missing `trade:execute` scope, or API-key access to the order's venue is disabled (`AUTH_INSUFFICIENT_SCOPE`). This endpoint is also behind the service-token/CSRF/API-key mutation gate, whose own rejections are `403` with a minimal `error` body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"404":{"description":"No order with that id, or the order belongs to another user — both answer the same way (`VALIDATION_INVALID_ORDER`) so an id is not a probe.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"409":{"description":"The venue has no native amend or is unknown (`EXCHANGE_AMEND_UNSUPPORTED`), or the request carried a `quantity` (`EXCHANGE_AMEND_QUANTITY_UNSUPPORTED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"429":{"description":"Order rate limit exceeded — an amend charges the same limiter a submit does. Carries `VALIDATION_INVALID_ORDER`; match the status, not the code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"The order could not be read (`INTERNAL_ERROR`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"503":{"description":"Execution disabled by a circuit breaker (`MARKET_PAUSED`), no executor registered for the venue (`EXCHANGE_ERROR`), venue credentials unavailable (`AUTH_CREDENTIALS_NOT_FOUND`), or the API-key provider-access lookup was unavailable (reported with `AUTH_INSUFFICIENT_SCOPE` — match the status here, not the code).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/orders/cancel-batch":{"post":{"operationId":"cancelBatchOrders","summary":"Cancel a specific set of orders atomically at the selection layer","description":"Validates the ENTIRE selection before sending any venue request — if any requested order is missing, not owned by the caller, already terminal, on a mismatched exchange, or has no exchange order id yet (pre-submission), the whole request is rejected with no venue call made and no partial cancels. Once validation passes, the venue's native batch-cancel endpoint determines the per-order result; a venue-refused cancel is reported in `failures` without marking the local row cancelled.\nAll selected orders must belong to the SAME `exchange_id` — a mixed-venue batch is rejected with `400`.\n**Auth & scope.** Requires `trade:execute`.","tags":["Orders"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderCancelBatchRequest"}}}},"responses":{"200":{"description":"Per-order batch-cancel outcome.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderCancelBatchResponse"}}}},"400":{"description":"Empty `order_ids` (after de-duplication), more than 100 ids, the selected orders span more than one `exchange_id`, no executor is registered for that exchange, or the venue rejected the batch-cancel as an unsupported request. Empty body (status code only)."},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"One of the selected orders belongs to another user, missing `trade:execute` scope, or API-key access to the provider is disabled. Empty body (status code only)."},"404":{"description":"One of the requested order ids does not exist. Empty body (status code only)."},"409":{"description":"One of the requested orders is already terminal, or has no exchange order id yet (pre-submission). Empty body (status code only)."},"500":{"description":"Database read/write failed, or the venue's batch-cancel call failed unexpectedly. Empty body (status code only)."}}}},"/orders/cancel-all":{"post":{"operationId":"cancelAllOrders","summary":"Cancel all of the authenticated user's open orders on an exchange","description":"Kill-switch endpoint: cancels every open order the caller has on `exchange_id` (optionally scoped further to a single `market_id`) via the venue's native cancel-all call, then reconciles local `Order` rows up to however many the venue actually reported cancelled. If the venue reports `0` cancellations, no local rows are touched — a mismatch between \"0 cancelled\" and locally-tracked active orders is reconciled automatically in the background rather than being blindly marked cancelled.\nUnlike `POST /orders/{order_id}/cancel` and `POST /orders/cancel-batch`, input errors here (e.g. an unsupported `exchange_id`) surface as a real `400`, not a `success: false` 200 — this is the kill-switch path, so a caller-input problem must be visibly distinct from \"the venue kept orders resting.\"\n**Auth & scope.** Requires `trade:execute`.","tags":["Orders"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderCancelAllRequest"}}}},"responses":{"200":{"description":"Cancel-all result. `cancelled_count` is the venue's authoritative count; `cancelled_order_ids` lists the local rows actually updated (may be fewer than `cancelled_count` if some local rows lag).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderCancelAllResponse"}}}},"400":{"description":"No executor is registered for `exchange_id` — `VALIDATION_INVALID_ORDER`, message `unknown exchange_id '<id>'` — or the venue rejected the cancel-all call as an invalid request (e.g. bad `market_id`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"401":{"description":"The user's venue credentials could not be loaded (`AUTH_CREDENTIALS_NOT_FOUND`). NOTE this is a `401`, not a `500`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"403":{"description":"Missing `trade:execute` scope (`AUTH_INSUFFICIENT_SCOPE`), or API-key access to this provider is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"An unclassified internal failure. Per-order local-row update failures are NOT errors — they only increment `failed_count` in a `200` response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"502":{"description":"The venue's cancel-all call failed (network/exchange error).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"503":{"description":"API-key access to this provider could not be checked (`INTERNAL_ERROR`, fails closed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"504":{"description":"`NETWORK_TIMEOUT` — the venue's cancel-all call timed out.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/orders/fee-quote":{"get":{"operationId":"getFeeQuote","summary":"Get a combined platform + exchange fee quote for a prospective trade","description":"Prices a trade you are considering WITHOUT submitting it: for a market order (`order_type=market`, `price` omitted) the live orderbook is walked for `quantity` to compute a size-weighted executable price; for a limit order (`order_type=limit`) `price` is required and used as the resting quote. Shares the exact computation used by the WebSocket RFQ stream (`subscribe_fee_quote`), so the two surfaces can never price a trade differently.\nQuotes are display-only, degrade-open estimates (`is_estimate: true`) — the authoritative fee is computed at fill time. If no fresh orderbook is available, every numeric field is a `\"0\"` placeholder and `pricing_unavailable: true`; callers MUST check that flag rather than rendering the zeros as a real quote.\nSupported `exchange_id` values: `polymarket`, `kalshi`, `predictfun`.\n**Auth & scope.** Requires `trade:read`.","tags":["Orders"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:read","parameters":[{"name":"exchange_id","in":"query","required":true,"description":"One of `polymarket`, `kalshi`, `predictfun`.","schema":{"type":"string","enum":["polymarket","kalshi","predictfun"],"example":"polymarket"}},{"name":"market_id","in":"query","required":false,"description":"Condition id (Polymarket). Required for Polymarket if `token_id` is omitted.","schema":{"type":"string"}},{"name":"token_id","in":"query","required":false,"description":"CLOB token id (Polymarket) — preferred over `market_id` for book/fee lookup.","schema":{"type":"string","example":"71360012345678901234567890123456789012345678901234567890123456"}},{"name":"quantity","in":"query","required":true,"description":"Decimal string. Number of shares/contracts to quote. Must be > 0.","schema":{"type":"string","example":"100"}},{"name":"side","in":"query","required":true,"description":"The quote is side-aware (walks the ask side for buy, the bid side for sell).","schema":{"type":"string","enum":["buy","sell"],"example":"buy"}},{"name":"price","in":"query","required":false,"description":"Decimal string in `(0, 1]`. Required when `order_type=limit` (the resting price). Omit for `order_type=market` to have the server price off the live book.","schema":{"type":"string","example":"0.52"}},{"name":"order_type","in":"query","required":true,"description":"`market` (taker, priced from the book) or `limit` (maker, priced at `price`).","schema":{"type":"string","enum":["market","limit"],"example":"market"}}],"responses":{"200":{"description":"Combined platform + exchange fee quote.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderFeeQuoteResponse"}}}},"400":{"description":"Unsupported `exchange_id`, invalid `order_type`/`side`, non-positive `quantity`, `price` out of `(0, 1]`, a limit order missing `price`, or a Polymarket quote missing both `token_id` and `market_id`. Empty body (status code only) — the specific validation failure is logged server-side but never returned."},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Missing `trade:read` scope, or API-key access to this provider is disabled. Empty body (status code only)."},"503":{"description":"API-key access to this provider could not be checked (fails closed). Empty body (status code only)."}}}},"/positions/exposure":{"get":{"operationId":"getPositionsExposure","summary":"Get the authenticated user's current live position exposure","description":"Returns the caller's current positions from the lowest-latency source available, which is always at least as fresh as the synchronous fill path. This is the RECOMMENDED source of truth for \"what do I currently hold\" while actively trading — prefer it over any aggregate that may lag behind live fills.\nThree buckets, each token appears in at most one:\n  - `positions` — open positions (`net_size != 0`), with `available_to_sell`\n    (net size minus outstanding sell reservations) and `reserved`.\n  - `closed_token_ids` — tokens the store just folded flat (within a\n    10-minute window); callers may use presence here to zero any\n    stale cached position for that token.\n  - `resolved` — held tokens whose MARKET HAS RESOLVED, split out so a\n    resolved loser is never confused with an open position; `redeemable`\n    is `true` for a won (claimable) outcome, `false` for a loss.\n\nAbsence from every bucket means the store has no current opinion on that token (neither \"open\" nor \"just closed\") — leave any previously-cached value as-is.\n**Central fallback.** On a deployment without a cell-local position store the response is built from central Postgres instead. In that mode `available_to_sell` always equals `net_size`, `reserved` is always `0`, and `closed_token_ids` is always empty — the reservation view is a regional-node feature.\n**Auth & scope.** Requires `position:read`. Takes no query, path or body parameters.","tags":["Orders"],"x-kairos-auth":"api-key","x-kairos-scope":"position:read","responses":{"200":{"description":"Current position exposure.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderPositionsExposureResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Missing `position:read` scope, or API-key access to one of the returned providers is disabled. Empty body (status code only)."},"500":{"description":"Backing store read failed. Empty body (status code only)."},"503":{"description":"API-key access to one of the returned providers could not be checked (fails closed). Empty body (status code only)."}}}},"/v2/orders/intent":{"post":{"operationId":"createOrderIntent","summary":"Build an unsigned EIP-712 order payload for self-custody signing (step 1 of 2)","description":"The first step of the \"external-signing\" / bring-your-own-key institutional lane (Polymarket and Predict.fun, EOA only): the server builds the canonical EIP-712 typed-data message for the order you describe and stashes it single-use in Redis under a fresh `payload_id` (60 s TTL). NO order is placed and NOTHING is signed by Kairos — the response gives you a 32-byte digest (or the full typed-data payload) to sign yourself, off your own key, then hand back to `POST /v2/orders/submit`. This removes the custodial signing hop from the order path for approved market makers / institutional desks.\n**Venue.** `provider` selects the venue: `polymarket` (default) or `predictfun`. On Predict.fun you supply nothing venue-specific — the server resolves the market's `isYieldBearing`/`isNegRisk` pair (which selects one of four verifying contracts), its `feeRateBps` (part of the signed struct) and its price tick from authoritative metadata, and cross-checks your `intent.neg_risk` against the market. Predict.fun's signed `Order` struct is NOT the same shape as Polymarket's.\n`market_id` and `outcome` are METADATA ONLY — they are never part of the signed digest, so they can't be tampered with post-signing — but both are REQUIRED: the server resolves `intent.token_id` against `market_id` and rejects a mismatch, a blank `market_id`, or a missing `outcome` with `400`.\nRecommendation: omit `intent.salt` and `intent.timestamp_ms` and let the server stamp them — this guarantees a fresh digest per intent. Only set them yourself if you need to reproduce a digest deterministically (or if you're building the order fully client-side over the one-RTT WebSocket `submit_signed_order` command instead of this two-RTT REST flow, which REQUIRES you to set both — and is Polymarket-only).\n**Allowlist.** Restricted to `User.executionFastlaneEnabled` accounts, flipped by a Kairos admin during onboarding — an unapproved account gets `403 external-signing execution is not enabled for this account`, never a bare `401`, so a valid-but-unapproved caller gets an unambiguous signal. The gate fails closed on a DB error, which is also a `403` (`external-signing authorization check failed`) rather than a `500`. The flag is read through a short-lived cache, so a revocation takes effect within a couple of seconds rather than instantly.\n**Only `signature_type: 0` (EOA) is accepted** on this lane — Poly1271 / smart-wallet signature types are rejected at intake (`400`) rather than failing later at `/submit` with an opaque signer mismatch, and `signer_address` (if sent) must equal `owner_address`.\n**Admission.** The same size/price bounds, time-in-force and post-only capability gates, circuit breakers and per-user order rate limit that guard `POST /orders` are applied here, gated by the `FASTLANE_ADMISSION` deployment setting (`off` | `shadow` | `enforce`). Under `off`/`shadow` a narrower gate still runs: the execution circuit breakers and the post-only capability check. A rate-limit slot is charged HERE, on `/intent`, not on `/submit`.\n**Errors are the minimal `{\"error\": \"...\"}` shape** on this whole lane — no `error_details`, no `code`.\n**Auth & scope.** Requires `trade:execute` for the selected provider, AND the fastlane allowlist above. Unlike `POST /orders`, this endpoint does NOT require a service token / CSRF token — a session JWT alone is sufficient.","tags":["Orders"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderIntentRequest"}}}},"responses":{"200":{"description":"Unsigned EIP-712 payload + digest. Sign `eip712_digest_hex` as a raw 32-byte hash, or run `unsigned_payload` through `eth_signTypedData` — both yield an identical signature.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderIntentResponse"}}}},"400":{"description":"Intake validation, metadata resolution, admission or payload-build failure. Exact `error` strings:\n- `only signature_type 0 (EOA) is supported on the external-signing lane`\n- `signer_address must equal owner_address for signature_type 0 (EOA)`\n- `outcome is required for external orders (e.g. \"Yes\" / \"No\")` / `outcome too long (max 64 chars)`\n- `market_id is required for external orders` / `market_id too long (max 256 chars)` / `token_id too long (max 256 chars)`\n- `token_id does not belong to the supplied market_id` / `token_id does not match the supplied outcome`\n- `expiration_unix_secs is in the past for this GTD order`\n- `invalid intent: …` — wraps the venue payload builder: price outside `(0, 1]`, size not positive or over 2 decimal places, GTD without `expiration_unix_secs`, `post_only` with a non-resting TIF, unparseable `token_id`, amount/notional overflow\n- Predict.fun only: `neg_risk mismatch: market <id> is neg_risk=<x>, intent said <y>`, `could not load predict.fun market <id>`, `expiration_unix_secs is required for time_in_force=GTD`, `invalid price: …` / `invalid size: …` / `invalid amounts: …`\n- Under `FASTLANE_ADMISSION=enforce`, the shared admission rejections: `Quantity must be positive`, `Minimum order quantity is N shares`, `Maximum order quantity is 1000000 shares`, `Price must be positive`, `Price is required for all orders`, `Price must be <= 1 for <venue>`, `Minimum $5 notional value required for API buy orders, got $N`, and the TIF/post-only capability messages.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"`API key missing trade:execute scope`; `API-key access to <provider> is disabled`;\n`external-signing execution is not enabled for this account` (not allow-listed);\n`external-signing authorization check failed` (allowlist DB read failed — fails closed as a\n403, not a 500); or, under `FASTLANE_ADMISSION=enforce`, `Trading not enabled for user`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"429":{"description":"`Order rate limit exceeded` — the per-user (and per-credential, if your key has an `orders` override) order window. Only charged under `FASTLANE_ADMISSION=enforce`. No `Retry-After` header is sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"500":{"description":"`internal server error` — failed to resolve the outcome, serialize or store the intent, or a `payload_id` collision (astronomically unlikely). Under enforce-mode admission also `Exchange capabilities unavailable` and `Failed to get trading status`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"503":{"description":"`Execution disabled by circuit breaker: <reason>` or `Order rejected by circuit breaker: <reason>` (global/per-exchange halt, or a side/kind/TIF restriction — canary users bypass both); `API-key access to <provider> is unavailable` (access cache read failed); `predictfun execution is not available on this node`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}}}}},"/v2/orders/submit":{"post":{"operationId":"submitSignedOrder","summary":"Submit your externally-signed order signature (step 2 of 2)","description":"Completes the self-custody order flow started by `POST /v2/orders/intent`: hand back `payload_id` and the 65-byte signature you produced over the returned digest. The server atomically claims the stored intent with a Redis `GETDEL` (single-use — a replayed or concurrent second submit for the same `payload_id` gets `400 payload_id not found or expired`), recomputes the EIP-712 digest itself (never trusting anything in this request beyond the signature), `ecrecover`s the signer, confirms it matches the intent's declared owner AND is a wallet registered to the authenticated caller, then forwards the assembled signed order to the venue CLOB.\n**The claim happens BEFORE any verification**, so the `payload_id` is consumed even when the request goes on to fail — a bad signature burns the intent.\n**Synchronous result.** `status` is the venue's immediate result string, passed through verbatim. For a marketable order that crossed immediately this IS your fill confirmation; for a resting order it confirms the order is live. `exchange_order_id` is the venue's handle — alongside the internal `order_id` it is the durable identifier for cancels.\n**Async fills.** A resting order's later fills are NOT returned here — they stream over your authenticated `/ws` connection as `partially_filled` / `filled` / `status_changed` events (deduped on the venue trade id; automatic reconciliation ensures fills are never silently lost, just occasionally a beat slower without `/ws`). Poll `GET /orders/{order_id}` if you are not holding a WebSocket open.\n**A persistence failure does NOT fail the request.** If the order lands at the venue but the Kairos row cannot be written, the `200` is still returned and the failure is logged — treat the venue as authoritative.\n**Retries.** The claimed intent is single-use and deleted on claim — a retry or a reprice requires a brand-new `POST /v2/orders/intent` (fresh `salt`/`timestamp_ms` → fresh digest → fresh signature). There is no silent server-side re-sign. The rate-limit slot was already charged at `/intent` and is not charged again here.\n**Auth & scope.** Requires the fastlane allowlist (re-checked here, independent of the check at `/intent`) and `trade:execute` for the provider recorded on the stored intent. No service token / CSRF token is required.","tags":["Orders"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSubmitSignedRequest"}}}},"responses":{"200":{"description":"Venue's synchronous placement result.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSubmitSignedResponse"}}}},"400":{"description":"Exact `error` strings:\n- `payload_id not found or expired` — wrong / stale / already-claimed id\n- `payload_id expired` — the id resolved but its wall-clock `expires_at_us` has passed\n- `payload_id does not belong to authenticated user`\n- `invalid signature hex` — not decodable hex\n- `invalid signature: signature must be 65 bytes (got N)` / `invalid signature: failed to parse signature: …` / `invalid signature: ecrecover failed: …`\n- `signature recovered 0x… does not match owner 0x…` — usually the EIP-191-vs-raw-digest mistake\n- `recovered signer is not a registered wallet for this user` — also returned when the wallet lookup itself errors\n- `polymarket credentials not configured`\n- Predict.fun only: `predict.fun trading is not enabled for this account — no venue signing wallet is registered`; `this predict.fun account is a Kernel smart account, which the external-signing lane does not support yet …`; `owner_address is not your registered predict.fun trading wallet`\n- `venue returned an empty order id; order not tracked`\n- Under `FASTLANE_ADMISSION=enforce`, the re-run admission rejections (same set as `/intent`, minus the rate limit).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"`external-signing execution is not enabled for this account`; `external-signing authorization check failed`; `API key missing trade:execute scope`; `API-key access to <provider> is disabled`; or (enforce-mode admission) `Trading not enabled for user`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"500":{"description":"`internal server error` — Redis claim failure, stored intent no longer deserializes or rebuilds, the recomputed digest differs from the stored one (refused), or an internal failure assembling the order. The order may already be LIVE on the venue — verify via `GET /orders/{order_id}` or `/ws` before retrying.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"502":{"description":"`CLOB error: <venue message>` — the venue rejected the signed order (balance/allowance/tick); the venue's message is passed through verbatim.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"503":{"description":"`API-key access to <provider> is unavailable` (access cache read failed); `verification temporarily unavailable` (an EIP-1271 RPC timeout — reachable only on a lane that accepted a non-EOA signature type, so not expected today); or an enforce-mode circuit-breaker rejection. NOTE: the `payload_id` was already consumed, so a retry needs a fresh intent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}}}}},"/health":{"get":{"operationId":"getHealth","summary":"Shallow liveness probe","description":"The load balancer's liveness probe. **Unauthenticated** — the only endpoint on this service that is. Always returns `200`; a failed Redis ping is reported as `status: \"degraded\"` / `healthy: false` in the body rather than as a non-2xx status, so the probe never removes an instance that is still serving.\nThe deeper probes (`GET /health/deep`, `GET /executor/dead_letters`, `GET /internal/canary-health`) are gated by internal service tokens and are not reachable with an API key or a session JWT.","tags":["Orders"],"x-kairos-auth":"public","security":[],"responses":{"200":{"description":"Liveness state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderHealthResponse"}}}}}}},"/exchanges":{"get":{"operationId":"listExchanges","summary":"List the venue ids this deployment has registered","description":"Returns the raw registry ids (e.g. `polymarket`, `kalshi`, `predictfun`, `hyperliquid`, `opinion`) usable as `exchange_id` elsewhere in this API. Requires authentication but **no scope** — it exposes no user data.\nInfallible: it cannot return an error status.","tags":["Exchanges"],"x-kairos-auth":"api-key","responses":{"200":{"description":"Registered venue ids.","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}},"example":["polymarket","kalshi","predictfun","hyperliquid"]}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"}}}},"/exchanges/{exchange_id}":{"get":{"operationId":"getExchange","summary":"Get a venue's display info and full capability set","description":"Returns the venue's display name, active flag, and the complete `capabilities` object that drives per-venue order validation. Requires authentication but **no scope**.\nThe lookup canonicalizes aliases (`kalshi_offchain` resolves to `kalshi`), but the response's top-level `id` echoes what you asked for — read `capabilities.exchange_id` for the canonical id.","tags":["Exchanges"],"x-kairos-auth":"api-key","parameters":[{"name":"exchange_id","in":"path","required":true,"schema":{"type":"string","example":"polymarket"}}],"responses":{"200":{"description":"Venue info.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderExchangeInfo"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"404":{"description":"No such venue on this deployment. Empty body (status code only)."}}}},"/exchanges/{exchange_id}/capabilities":{"get":{"operationId":"getExchangeCapabilities","summary":"Get a venue's capability set","description":"The `capabilities` sub-object of `GET /exchanges/{exchange_id}`, on its own. This is the endpoint to consult before submitting an order: it tells you the venue's `supported_tif`, whether it honours `post_only`, its `min_tick_size` / `min_order_size` / `max_price`, and whether `POST /orders/cancel-all` is available (`supports_cancel_all`). Requires authentication but **no scope**.","tags":["Exchanges"],"x-kairos-auth":"api-key","parameters":[{"name":"exchange_id","in":"path","required":true,"schema":{"type":"string","example":"polymarket"}}],"responses":{"200":{"description":"The venue's capabilities.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderExchangeCapabilities"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"404":{"description":"No such venue on this deployment. Empty body (status code only)."}}}},"/exchanges/{exchange_id}/allowances":{"post":{"operationId":"setAllowances","summary":"Set the on-chain allowances the venue needs","description":"Grants the venue's contracts the token approvals they need, signed custodially with your delegated key. Idempotent in effect: an already-sufficient allowance is not re-sent, and the corresponding `*_tx_hash` comes back `null`.\nThe three identity fields in the body are required by the schema but are validated against the authenticated caller — they can confirm authority, never grant it. A mismatch, or a wallet you do not own, is a `403`.\nSelf-custody callers on the external-signing lane should use `POST /v2/onchain/intent` with `op: \"approvals\"` instead, and sign the approvals themselves.\n**Auth & scope.** Requires `trade:execute`, the mutation gate (API-key triple satisfies it), and a current Turnkey policy version.","tags":["Exchanges"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","parameters":[{"name":"exchange_id","in":"path","required":true,"schema":{"type":"string","example":"polymarket"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSetAllowancesRequest"}}}},"responses":{"200":{"description":"Approvals submitted (or already sufficient).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSetAllowancesResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Missing `trade:execute` scope, API-key access to this provider disabled, an identity field that does not match the authenticated caller (`AUTH_IDENTITY_MISMATCH`), a wallet you do not own, or installed Turnkey policies behind the required version (`AUTH_POLICY_OUTDATED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"404":{"description":"No allowance manager registered for this venue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"Credentials or signing authority could not be resolved, or a non-retryable approval submission failed. Submission failures return `ALLOWANCE_APPROVAL_FAILED` without exposing signer or custody internals.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"502":{"description":"An upstream approval dependency failed. Returns `ALLOWANCE_APPROVAL_FAILED`; re-read allowance state before retrying.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"503":{"description":"Approval submission is temporarily busy, or API-key access to this provider could not be checked (fails closed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"504":{"description":"Approval submission or confirmation timed out. Returns `ALLOWANCE_APPROVAL_FAILED`; re-read allowance state because the transaction may have landed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/orders/route-quote":{"get":{"operationId":"getRouteQuote","summary":"Price a size across both legs of an approved cross-venue market link","description":"Plans a buy across the two venues of an approved market link and returns the per-leg sizes, limit prices and fee estimates it would use, without submitting anything. `POST /orders/route-buy` executes the same plan.\n**A plan that cannot be built is still a `200`** with `routed: false` — no approved link for this market, sources unavailable, or fewer than two legs with a live book. Only caller-input errors are `400`.\n**Auth & scope.** Requires `trade:read` for `provider`.","tags":["Routing"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:read","parameters":[{"name":"provider","in":"query","required":true,"description":"The venue you are quoting from. Routable venues are `polymarket` and `predictfun`.","schema":{"type":"string","example":"polymarket"}},{"name":"market_id","in":"query","required":true,"schema":{"type":"string"}},{"name":"side","in":"query","required":true,"description":"`yes` or `no`, case-insensitive.","schema":{"type":"string","enum":["yes","no"]}},{"name":"quantity","in":"query","required":true,"description":"Decimal string, must be > 0.","schema":{"type":"string","example":"100"}},{"name":"slippage_cents","in":"query","required":false,"description":"Must be in `[1, 99]` if provided.","schema":{"type":"integer"}}],"responses":{"200":{"description":"The routing plan (check `routed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRouteQuoteResponse"}}}},"400":{"description":"`slippage_cents` outside `[1, 99]`, invalid `side`, non-positive `quantity`, or a malformed authenticated user id. Empty body (status code only)."},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Missing `trade:read` scope, or API-key access to `provider` is disabled. Empty body (status code only)."},"503":{"description":"API-key access to `provider` could not be checked (fails closed). Empty body (status code only)."}}}},"/orders/route-fees":{"get":{"operationId":"getRouteFees","summary":"Get the per-leg fee model for an approved cross-venue market link","description":"Returns the fee model each leg of the link charges per side, so a client can show the routing cost before quoting a size. Legs on non-routable venues are silently omitted. An unrecognized link is a `200` with `routed: false` and an empty `legs`.\n**Auth & scope.** Requires `trade:read` for `provider`.","tags":["Routing"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:read","parameters":[{"name":"provider","in":"query","required":true,"schema":{"type":"string","example":"polymarket"}},{"name":"market_id","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Per-leg fee models.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRouteFeesResponse"}}}},"400":{"description":"Malformed authenticated user id. Empty body (status code only)."},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Missing `trade:read` scope, or API-key access to `provider` is disabled. Empty body (status code only)."},"503":{"description":"API-key access to `provider` could not be checked (fails closed). Empty body (status code only)."}}}},"/orders/route-buy":{"post":{"operationId":"routeBuy","summary":"Buy a size split across both legs of an approved cross-venue market link","description":"Executes the plan `GET /orders/route-quote` previews: creates a parent route row and submits one child order per leg. Children go through the same validation, admission and rate limiting as `POST /orders`.\n**Partial success is a `200`.** `status` is always `submitting`; a leg that failed to submit has `order_id: null` and a populated `legs[].error`. Track the parent via `GET /orders/routes` and the children via `GET /orders/{order_id}`.\n**Feature-gated.** Disabled unless `ROUTED_EXECUTION_ENABLED` is set on the deployment; otherwise every call is a `403`.\n**Auth & scope.** Requires the mutation gate (API-key triple satisfies it). This handler enforces no scope of its own — each child order carries the normal `trade:execute` and per-provider checks.","tags":["Routing"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRouteBuyRequest"}}}},"responses":{"200":{"description":"Route created and children dispatched. Inspect `legs[].error` for per-leg failures.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRouteBuyResponse"}}}},"400":{"description":"`slippage_cents` outside `[1, 99]`, non-positive `quantity`, an unresolvable `side`/`outcome`, or a malformed authenticated user id. Empty body (status code only)."},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Routed execution is disabled on this deployment. Empty body (status code only)."},"422":{"description":"No approved market link for this market, no routable sources, or the planner produced no legs. Empty body (status code only)."},"500":{"description":"The parent route row could not be created. Empty body (status code only)."},"503":{"description":"Route sources could not be assembled. Empty body (status code only)."}}}},"/orders/route-close":{"post":{"operationId":"routeClose","summary":"Close a routed position across both legs of a market link","description":"Sells the holdings on each leg of an approved market link, at a per-leg floor price derived from that leg's best bid minus `slippage_cents` (never below `0.01`). Holdings are rounded down to the venue's share precision, and a leg with less than `0.01` sellable is skipped.\n**`leg_caps` is fail-closed:** if you send it, a leg with no matching entry is excluded from the close entirely rather than closed in full.\nLike `route-buy`, this is a `200` with `status: submitting` and per-leg `error` strings; it is gated by `ROUTED_EXECUTION_ENABLED`.\n**Auth & scope.** Requires the mutation gate; per-leg `trade:execute` and provider checks happen on each child order.","tags":["Routing"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRouteCloseRequest"}}}},"responses":{"200":{"description":"Close dispatched. Inspect `legs[].error`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderRouteCloseResponse"}}}},"400":{"description":"`slippage_cents` outside `[1, 99]`, an unresolvable `side`/`outcome`, or a malformed authenticated user id. Empty body (status code only)."},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Routed execution is disabled on this deployment. Empty body (status code only)."},"422":{"description":"No approved market link, or nothing sellable after rounding and `leg_caps` filtering. Empty body (status code only)."},"500":{"description":"The position read or the parent route insert failed. Empty body (status code only)."}}}},"/orders/routes":{"get":{"operationId":"listRoutes","summary":"List the caller's routed parent orders and their legs","description":"Returns routed parents (from `route-buy` / `route-close`) newest first, each with its legs and the child order id, status and fill state. Available regardless of the `ROUTED_EXECUTION_ENABLED` flag, so history stays readable after routing is switched off.\n**Note on timestamps:** `created_at` / `completed_at` here are naive local timestamps serialized without a timezone offset — unlike `Order.created_at`, which is RFC 3339.\n**Auth.** Authentication only — this endpoint enforces no scope.","tags":["Routing"],"x-kairos-auth":"api-key","parameters":[{"name":"limit","in":"query","required":false,"description":"Maximum routes to return. Defaults to 50 and is **not clamped** server-side.","schema":{"type":"integer","format":"int64","default":50}}],"responses":{"200":{"description":"The caller's routes, newest first.","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/OrderRouteView"}}}}},"400":{"description":"Malformed authenticated user id. Empty body (status code only)."},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"500":{"description":"Database read failed. Empty body (status code only)."}}}},"/credentials/invalidate":{"post":{"operationId":"invalidateCredentials","summary":"Drop the service's cached copy of your venue credentials","description":"Evicts the cached credential set for one venue (or all of them) so the next order re-reads it from storage. Use after rotating a venue API key or re-running an enable-trading flow, when the executor would otherwise keep using the stale credential for the cache's lifetime.\nDoes not delete or modify any stored credential.\n**Auth.** Authentication and self-ownership only — `user_id` must equal the authenticated caller. No scope is enforced and no mutation gate applies, so a bare session JWT is sufficient.","tags":["Exchanges"],"x-kairos-auth":"api-key","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderCredentialsInvalidateRequest"}}}},"responses":{"200":{"description":"Cache evicted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderCredentialsInvalidateResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"`user_id` does not match the authenticated caller. Empty body (status code only)."},"404":{"description":"An `exchange_id` was supplied but no credential provider is registered for it. Empty body (status code only)."}}}},"/combo/quote":{"post":{"operationId":"quoteCombo","summary":"Price a Polymarket combo (parlay) without executing it","description":"Returns the real maker-quoted blended price for a multi-leg Polymarket combo. A naive product of the individual leg prices does NOT match what the RFQ gateway quotes, so this is the only correct way to price a parlay before placing it.\nRead-only: it opens an RFQ, takes the quote, drops the connection, and commits nothing. Polymarket-only — combos exist on no other venue.\n**camelCase.** The whole `/combo/*` family uses camelCase request and response field names, where every Orders endpoint is snake_case — check the schema for the family you are calling rather than assuming one convention across this API. Prices and amounts are six-decimal fixed-point INTEGER strings (`\"16393\"` = 0.016393), not decimals.\n**Requires connected Polymarket credentials.** Every wallet type can quote (quoting never signs), including imported wallet types that cannot yet execute a combo.\n**Auth & scope.** Requires `trade:read` for `polymarket`. No mutation gate, so a session JWT alone works. Not rate limited.","tags":["Combo"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:read","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderComboQuoteRequest"}}}},"responses":{"200":{"description":"The maker's blended quote.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderComboQuoteResponse"}}}},"400":{"description":"`VALIDATION_INVALID_ORDER`. Triggers: fewer than 2 or more than 10 legs; `notionalUsd` not finite or not positive, or too small to round to a non-zero six-decimal amount; Polymarket credentials not configured; an imported wallet whose signature type could not be determined. Also the gateway outcomes, which are deliberately mapped to plain-English messages that never leak gateway status codes:\n- no maker is pricing the parlay right now (can happen while a leg is in play)\n- no maker will take a parlay this size\n- the quote expired before it could be filled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Missing `trade:read` scope, or API-key access to `polymarket` is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"`INTERNAL_ERROR` — credential load failed, the credential set is not Polymarket, RFQ gateway authentication failed, or any other unmapped gateway error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"503":{"description":"API-key access to `polymarket` could not be checked (fails closed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/combo/execute":{"post":{"operationId":"executeCombo","summary":"Buy a Polymarket combo (parlay) via the RFQ gateway","description":"Runs the whole requester flow server-side in one call: creates the RFQ from your chosen leg position ids, accepts the best quote with a signed order, and returns the on-chain result. It is one call rather than two precisely to beat the quote expiry — there is no separate accept step.\n**Set `maxPriceCents`.** Without it you accept whatever blended price the maker returns. With it, a quote worse than your ceiling is refused and nothing trades (a `400`).\n**One-time collateral approval.** Combos settle against Polymarket's v3 exchange, which ordinary trading never approves. The first execute transparently sets that approval — directly for an EOA, or through the Polymarket relayer for a Kairos deposit wallet. **Imported smart-account wallets (Safe / imported Poly1271) cannot execute combos yet** and get a `400` explaining so; quoting still works for them.\n**Fees.** The Kairos platform fee is resolved from your tier BEFORE the irreversible RFQ is submitted (a tier-lookup outage stops the trade rather than making it free), then accrued against the pUSD spent through the same ledger and sweep as ordinary fills.\n**Auth & scope.** Requires `trade:execute` for `polymarket` plus the service-token/CSRF/API-key mutation gate, which the API-key triple satisfies automatically.","tags":["Combo"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderComboExecuteRequest"}}}},"responses":{"200":{"description":"The combo settled on-chain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderComboExecuteResponse"}}}},"400":{"description":"`VALIDATION_INVALID_ORDER`. All the `POST /combo/quote` triggers, plus: the wallet is an imported smart-account type that cannot execute combos yet; and the slippage refusal — the maker's quote came back above `maxPriceCents`, so nothing traded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Missing `trade:execute` scope, or API-key access to `polymarket` is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"`INTERNAL_ERROR` — credential load failed, no regional signing authority on the credentials, the v3 collateral approval failed, the relayer is not configured, the fee-tier lookup failed (fails closed — the trade is refused rather than made free), gateway auth failed, or any other unmapped gateway error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"503":{"description":"API-key access to `polymarket` could not be checked (fails closed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/combo/positions":{"get":{"operationId":"listComboPositions","summary":"List the authenticated user's Polymarket combo positions","description":"Returns the combos held by the caller's Polymarket maker wallet, newest/most-valuable first, each with its per-leg resolution state. Capped at 50 combos; there is no pagination parameter and no query parameters at all.\n**At the combo level, read `redeemable`, not `status`.** A won-but-unredeemed combo keeps `status: \"OPEN\"`, so inferring redeemability from `status` — or from leg prices — is wrong. `redeemable` is the authoritative flag and is `true` only for a resolved win with a non-zero balance; it covers \"not resolved\", \"lost\" and \"already redeemed\" in one check.\n**At the leg level, `legStatus` IS the authoritative field.** Use `legStatus` (`OPEN` / `RESOLVED_WIN` / `RESOLVED_LOSS`) to mark a leg won, lost or pending. Never infer a leg's outcome from `currentPrice` — the price is display data and a heuristic over it will be wrong. The caution above is about the combo-level `status` field specifically, and does not extend to `legStatus`.\nA parlay is all-or-nothing: one `RESOLVED_LOSS` leg makes the whole combo worth $0 even while sibling legs are still pending.\n**Auth & scope.** Requires `position:read` for `polymarket`. No mutation gate.","tags":["Combo"],"x-kairos-auth":"api-key","x-kairos-scope":"position:read","responses":{"200":{"description":"The caller's combo positions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderComboPositionsResponse"}}}},"400":{"description":"`VALIDATION_INVALID_ORDER` — Polymarket credentials not configured, or an imported wallet whose signature type could not be determined.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Missing `position:read` scope, or API-key access to `polymarket` is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"`INTERNAL_ERROR` — credential load failed, or the upstream combo-positions read failed or returned an unparseable body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"503":{"description":"API-key access to `polymarket` could not be checked (fails closed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/combo/cash-out-quote":{"post":{"operationId":"quoteComboCashOut","summary":"Price a combo cash-out (SELL) without executing it","description":"Returns the real maker-quoted proceeds for selling an open combo, and subtracts the Kairos platform fee so the preview matches what actually lands in the wallet. A client-side `shares × ∏(leg price)` estimate ignores the maker's spread and systematically overstates the payout — show `netProceedsE6`, not a local calculation.\nThe platform-fee lookup **fails closed**: a tier-lookup error rejects the quote rather than displaying an optimistic zero fee.\n**Fully-resolved and dead combos are rejected up front.** No maker quotes a settled binary, so the RFQ would simply hang; a combo that has fully resolved, or that has any single `RESOLVED_LOSS` leg, gets an immediate `400` instead. A won, fully-resolved combo is redeemed via `POST /combo/redeem`, not cashed out.\n**Rate limited** at 20 quotes/minute per user (`COMBO_QUOTE_RATE_LIMIT_PER_MIN`), fail-closed, because each call holds an external RFQ websocket open until the gateway timeout. The `429` carries `error_details.code = EXCHANGE_POLYMARKET_RATE_LIMITED`. No `Retry-After` header.\n**Auth & scope.** Requires `position:read` for `polymarket` AND the mutation gate (it is a POST behind `RequireServiceToken` despite being read-only), so an API-key triple or CSRF token is needed — a bare Bearer JWT is not enough.","tags":["Combo"],"x-kairos-auth":"api-key","x-kairos-scope":"position:read","x-kairos-rate-limit":"20/minute per user","x-kairos-bucket":"combo-quote","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderComboCashOutQuoteRequest"}}}},"responses":{"200":{"description":"Gross proceeds, platform fee, and net proceeds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderComboCashOutQuoteResponse"}}}},"400":{"description":"`VALIDATION_INVALID_ORDER` — fewer than 2 or more than 10 legs; `shares` not finite or not positive; credentials not configured; the parlay has a lost leg and can no longer pay out; the parlay has fully resolved (redeem it instead); or a gateway no-quote / size / expiry outcome.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Missing `position:read` scope, or API-key access to `polymarket` is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"429":{"description":"Combo-quote rate limit exceeded, or its Redis was unreachable (fails closed). `error_details.code = EXCHANGE_POLYMARKET_RATE_LIMITED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"`INTERNAL_ERROR` — credential load failed, the upstream positions read failed, the fee-tier lookup failed (fails closed), gateway auth failed, or another unmapped gateway error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"503":{"description":"API-key access to `polymarket` could not be checked (fails closed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/combo/cash-out":{"post":{"operationId":"cashOutCombo","summary":"Sell an open combo position back to pUSD","description":"Sells `shares` of an open combo through the RFQ gateway. Preview it first with `POST /combo/cash-out-quote`.\n**`minProceedsUsd` is checked against GROSS proceeds**, before the platform fee is deducted — so the amount that lands can be below the floor you set by the fee amount. Size the floor accordingly.\nSame up-front rejection as the quote endpoint: a combo with a lost leg, or one that has fully resolved, cannot be cashed out (no maker quotes a worthless or settled combo) and fails fast rather than hanging. Unlike the quote endpoint this is not rate limited — selling a position is economically self-limiting.\n**Auth & scope.** Requires `trade:execute` for `polymarket` plus the mutation gate.","tags":["Combo"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderComboCashOutRequest"}}}},"responses":{"200":{"description":"The cash-out settled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderComboCashOutResponse"}}}},"400":{"description":"`VALIDATION_INVALID_ORDER` — the same set as `POST /combo/cash-out-quote`, plus the floor refusal: the maker's quote came back below `minProceedsUsd`, so nothing traded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Missing `trade:execute` scope, or API-key access to `polymarket` is disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"`INTERNAL_ERROR` — credential load failed, the upstream positions read failed, the fee-tier lookup failed, gateway auth failed, or another unmapped gateway error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"503":{"description":"API-key access to `polymarket` could not be checked (fails closed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/combo/redeem":{"post":{"operationId":"redeemCombo","summary":"Redeem a resolved, winning combo back to pUSD","description":"The settlement path a cash-out cannot cover: once every leg has resolved there is no maker to sell to, so a winning combo is redeemed on-chain instead. Executed as a Polymarket relayer `WALLET` batch (approve the combo Router as operator, then redeem), signed by the deposit wallet's owner EOA through the user's delegated Turnkey key.\n**Kairos deposit wallets only.** Imported and plain-EOA makers get a `400` — the direct redemption path for them is not built yet.\n**Idempotent by construction.** The amount comes from the position's real on-chain share balance, never from the request, and the call is validated against `GET /combo/positions` first. A retry after a successful redeem sees a zero balance and is rejected, rather than firing a second batch that would revert on-chain while the client reads \"redeemed\".\n**Auth & scope.** Requires `trade:execute` for `polymarket`, the mutation gate, and a current Turnkey policy version for the combo-redeem capability specifically (`403 AUTH_POLICY_OUTDATED` if stale).","tags":["Combo"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderComboRedeemRequest"}}}},"responses":{"200":{"description":"The redemption batch was mined.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderComboRedeemResponse"}}}},"400":{"description":"`VALIDATION_INVALID_ORDER` — the wallet is not a Kairos deposit wallet; the `comboConditionId` is malformed; no combo with that condition id exists on this wallet; the parlay is not redeemable (not resolved as a win yet, or already redeemed); or it has no redeemable share balance.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Missing `trade:execute` scope, API-key access to `polymarket` disabled, or installed Turnkey policies behind the version the combo-redeem capability requires (`AUTH_POLICY_OUTDATED`, action `update_policies`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"`INTERNAL_ERROR` — credential load failed, the upstream positions read failed, the relayer is not configured, or the redemption batch failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"503":{"description":"API-key access to `polymarket` could not be checked (fails closed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/orders/market-links":{"post":{"operationId":"createMarketLink","summary":"Create (or preview) a self-serve cross-venue market link","description":"Declares that two markets on two different venues are the same real-world contract, making them routable together by `GET /orders/route-quote`, `POST /orders/route-buy` and `POST /orders/route-close`.\n**Two-step by design.** `confirm: false` (the default) returns a verified preview and writes nothing. Show the user the resolved pairing, then call again with `confirm: true` AND the preview's `verification_fingerprint` as `fingerprint`. The confirm re-resolves against fresh metadata and requires the result to fingerprint-match what was reviewed, so index drift between the two calls becomes a `409` (\"review the pairing again\") rather than a silently different link.\n**You attest; the server verifies.** Both legs are resolved against indexed market metadata and the link is REFUSED unless the venues' yes/no outcome labels pair exactly (case-insensitive). Outcome inversion is the one catastrophic failure mode here, so it is gated deterministically rather than by the user's click — a `Yes/No` vs `Trump/Harris` pair may well be the same market, but the mapping is not machine-provable and is refused rather than guessed.\n**Scope of a created link.** Links are approved but USER-SCOPED: routable only by their creator, so a bad self-link's blast radius is the creator's own wallet. Capped at 25 links per user, enforced inside the insert transaction so concurrent confirms cannot overshoot it.\nExpiry gaps and resolution-source divergence come back as `warnings`, not errors — for a self-scoped link they are the creator's risk to accept.\n**Rate limited** at 15 requests/minute per user (`MARKET_LINK_RATE_LIMIT_PER_MIN`), fail-closed. Previews count: every call costs two indexed-metadata lookups plus duplicate checks, and the 25-link cap only bounds completed inserts.\n> **Error shape.** This endpoint returns `{\"message\": \"...\"}` — NOT the `{\"error\": ...}` used everywhere else on this service, and with no `code` field. See `OrderMarketLinkErrorResponse`.\n**Auth.** Requires authentication and the mutation gate (API-key triple, `X-Service-Token`, or CSRF token). It enforces **no scope** — any authenticated credential that clears the mutation gate can create links.","tags":["Routing"],"x-kairos-auth":"api-key","x-kairos-rate-limit":"15/minute per user","x-kairos-bucket":"market-link","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderCreateMarketLinkRequest"}}}},"responses":{"200":{"description":"A verified preview, a newly created link, or an existing routable link — read `status` to tell which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderMarketLinkResponse"}}}},"400":{"description":"`invalid user id`; `exactly two legs required`; or `confirm requires the preview's verification fingerprint`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderMarketLinkErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"409":{"description":"`one of these markets is already claimed by another link`; `one of these markets was just claimed by another link` (lost insert race); or `market metadata changed since the preview — review the pairing again` (fingerprint mismatch).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderMarketLinkErrorResponse"}}}},"422":{"description":"The pairing is not verifiable. Messages: `<provider> is not a routable venue`; `legs must be on two different venues`; `<provider>: market <id> is not indexed`; `<provider>: market is settled|closed|resolved`; `<provider>: self-serve linking not supported`; `polymarket: not a binary market (N outcomes)`; `polymarket: outcome labels unavailable` / `token ids unavailable` / `condition id unavailable`; `predictfun: yes token unavailable` / `no token unavailable` / `outcome labels unavailable`; `outcome labels differ (A/B vs C/D) — the side mapping can't be verified`; `link limit reached (25 per user)`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderMarketLinkErrorResponse"}}}},"429":{"description":"`too many link requests — wait a moment and try again`. No `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderMarketLinkErrorResponse"}}}},"500":{"description":"`link lookup failed` or `link insert failed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderMarketLinkErrorResponse"}}}},"502":{"description":"`market metadata lookup failed` — the indexed-metadata query failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderMarketLinkErrorResponse"}}}}}}},"/exchanges/{exchange_id}/prepare-wallet":{"post":{"operationId":"prepareWallet","summary":"Sponsor gas and set the venue's required allowances on a wallet","description":"The one-time onboarding step behind \"enable trading\": tops the wallet up with sponsored gas if it needs it, then sets every token allowance the venue's contracts require. Both legs are signed custodially with the user's delegated key.\n**`wait_for_confirmation` defaults to `true`.** The call then blocks until the work completes and the response reports what actually happened. Passing `false` returns immediately with `confirmation_pending: true`, `success: true`, `gas_sponsored: false` and `allowances_set: 0` — **`success: true` there means \"accepted\", not \"done\"**, and a background failure is only logged, never surfaced. Poll the RPC query `exchange.getAllowances` to find out whether it worked.\nThe three identity fields are mandatory but are validated against the authenticated caller and the wallets it owns; they can confirm authority, never grant it.\nSelf-custody callers on the external-signing lane should use `POST /v2/onchain/intent` with `op: \"approvals\"` instead and pay their own gas.\n**Auth & scope.** Requires `trade:execute`, the mutation gate (this endpoint spends gas-station funds, so a stolen JWT alone must not reach it), wallet ownership, and a current Turnkey policy version.","tags":["Exchanges"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","parameters":[{"name":"exchange_id","in":"path","required":true,"schema":{"type":"string","example":"polymarket"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderPrepareWalletRequest"}}}},"responses":{"200":{"description":"Wallet prepared, or — when `wait_for_confirmation` was sent as false — preparation accepted and running in the background.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderPrepareWalletResponse"}}}},"400":{"description":"`user_id` is not a valid UUID, or an identity field is otherwise unusable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Missing `trade:execute` scope, API-key access to this provider disabled, an identity field that does not match the authenticated caller (`AUTH_IDENTITY_MISMATCH`), a wallet the caller does not own, or installed Turnkey policies behind the required version (`AUTH_POLICY_OUTDATED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"404":{"description":"No pre-trade service is registered for this venue.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"Credentials or regional signing authority could not be resolved, or the wallet preparation itself failed. Only reachable on the blocking path — a background failure never surfaces to the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"503":{"description":"API-key access to this provider could not be checked (fails closed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/exchanges/polymarket/enable-trading":{"post":{"operationId":"enablePolymarketTrading","summary":"Provision Polymarket CLOB credentials and set on-chain approvals","description":"The one-time onboarding step for Polymarket. In a single call the server derives CLOB API credentials for your wallet (signing the ClobAuth EIP-712 message with your delegated key), encrypts and stores them, and sets the token approvals the venue needs. Until this succeeds, `POST /orders` on `polymarket` will fail.\n**Credentials are never returned.** The derived API key/secret/passphrase are stored server-side only — deliberately kept out of HTTP responses so they cannot leak through logs, proxies or browser devtools.\nLegacy-EOA users get gas sponsorship plus approvals here. Deposit-wallet users had their approvals set by the onboarding batch (`POST /exchanges/polymarket/deposit-wallet/onboard`), so the approval legs are skipped and the `*_tx_hash` fields are omitted.\n**Auth & scope.** Requires `trade:execute` for `polymarket`, wallet ownership, and the mutation gate — this provisions signing credentials and is in the same risk class as order submission.","tags":["Onboarding"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderPolymarketEnableTradingRequest"}}}},"responses":{"200":{"description":"Credentials provisioned and approvals set.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderPolymarketEnableTradingResponse"}}}},"400":{"description":"`ValidationInvalidOrder` — `wallet_address` is not a valid address, or the authenticated user id is not a UUID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"`AuthCredentialsInvalid` — \"This API key lacks the trade:execute scope\"; `AuthInsufficientScope` / API-key access to `polymarket` disabled; or `AuthTurnkeyWalletNotFound` — \"Wallet not found or does not belong to you\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"Database read failed, credential derivation against Polymarket failed, or the approval transactions failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"503":{"description":"API-key access to `polymarket` could not be checked (fails closed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/exchanges/polymarket/enable-imported-trading":{"post":{"operationId":"enablePolymarketImportedTrading","summary":"Provision CLOB credentials for an imported Polymarket wallet","description":"The equivalent of `POST /exchanges/polymarket/enable-trading` for a wallet imported from polymarket.com. Derives and stores CLOB credentials against the imported EOA. No approval legs run — an imported wallet already carries its on-chain approvals from its prior polymarket.com activity.\nNeither `wallet_address` nor `turnkey_org_id` is trusted input: both are checked against the authenticated caller before anything is minted, so a stolen session cannot provision credentials against a wallet or sub-org it has no relationship to.\n**Rate limited** at 5 requests/minute per user (`CLOB_PROVISIONING_RATE_LIMIT_PER_MIN`, bucket `clob-provisioning`), fail-closed — each allowed call makes a real CLOB round-trip and writes a credential row. The `429` carries `error_details.code = VALIDATION_INVALID_ORDER` with the message `Too many import attempts — please wait a moment and try again`.\n**Auth & scope.** Requires `trade:execute` for `polymarket` and the mutation gate.","tags":["Onboarding"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","x-kairos-rate-limit":"5/minute per user","x-kairos-bucket":"clob-provisioning","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderPolymarketEnableImportedTradingRequest"}}}},"responses":{"200":{"description":"Imported-wallet credentials provisioned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderPolymarketEnableImportedTradingResponse"}}}},"400":{"description":"Malformed wallet address or user id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Missing `trade:execute` scope, API-key access to `polymarket` disabled, the supplied `turnkey_org_id` is not the caller's org, or the wallet is not the caller's.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"429":{"description":"CLOB-provisioning rate limit exceeded (or its Redis was unreachable — fails closed). No `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"Credential derivation or persistence failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"503":{"description":"API-key access to `polymarket` could not be checked (fails closed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/exchanges/predictfun/enable-trading":{"post":{"operationId":"enablePredictfunTrading","summary":"Set the on-chain approvals Predict.fun needs","description":"Predict.fun has no server-side credentials to provision — orders are signed at submission time. This endpoint does the one-time on-chain setup instead: it approves USDT and ConditionalTokens across all four `(yieldBearing × negRisk)` market variants, gas-sponsored on BSC. A wallet must run this once before its first Predict.fun order; the executor does not approve per trade.\n**Idempotent.** Variants already approved are no-ops. A wallet that was only partially approved re-submits just the missing variants on the next call, so retrying is safe.\n> **Error shape.** This endpoint returns the minimal `{\"error\": \"...\", \"code\": \"...\"}` body (`OrderSimpleErrorResponse`), NOT the structured `error_details` envelope used by the Polymarket and Opinion equivalents. Codes: `INSUFFICIENT_SCOPE`, `PLATFORM_API_ACCESS_DISABLED`, `RATE_LIMITED`, `INVALID_USER_ID`, `DATABASE_ERROR`.\n**Rate limited** at 5 requests/minute per user (bucket `clob-provisioning`, shared with the other heavy-provisioning endpoints), fail-closed, and checked BEFORE the ownership query so a flood cannot load the database. Each allowed call can launch up to 12 sponsored BSC approvals.\n**Auth & scope.** Requires `trade:execute` for `predictfun`, wallet ownership, and the mutation gate.","tags":["Onboarding"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","x-kairos-rate-limit":"5/minute per user","x-kairos-bucket":"clob-provisioning","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderPredictfunEnableTradingRequest"}}}},"responses":{"200":{"description":"Approvals set (or already present).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderPredictfunEnableTradingResponse"}}}},"400":{"description":"`INVALID_USER_ID`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"`INSUFFICIENT_SCOPE` (\"This API key lacks the trade:execute scope\"), `PLATFORM_API_ACCESS_DISABLED`, or the wallet is not owned by the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"429":{"description":"`RATE_LIMITED` — \"Too many enable-trading attempts — please wait a moment and try again\". No `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"500":{"description":"`DATABASE_ERROR`, or an approval transaction failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"503":{"description":"API-key access to `predictfun` could not be checked (fails closed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}}}}},"/exchanges/opinion/enable-trading":{"post":{"operationId":"enableOpinionTrading","summary":"Provision Opinion credentials and set the USDT allowance","description":"One-time onboarding for Opinion: auto-provisions Opinion API credentials through their builder API, encrypts and stores them, then sets the USDT allowance on BSC (gas-sponsored).\n**Known recovery case.** If Opinion already has the wallet on file but Kairos has lost the original API key, and Opinion's recovery endpoint is unavailable, the call returns `400` with `error_details.code = AUTH_CREDENTIALS_NOT_FOUND` and actions pointing at Kairos support (email + Discord). That state cannot be resolved by retrying — the Opinion account must be reset manually.\n**Auth & scope.** Requires `trade:execute` for `opinion`, wallet ownership, the mutation gate, and a current Turnkey policy version.","tags":["Onboarding"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderOpinionEnableTradingRequest"}}}},"responses":{"200":{"description":"Credentials provisioned and USDT approved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderOpinionEnableTradingResponse"}}}},"400":{"description":"`ValidationInvalidOrder` — invalid user id; or `AuthCredentialsNotFound` — the manual-reset recovery case described above.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Missing `trade:execute` scope, API-key access to `opinion` disabled, wallet not owned by the caller, or installed Turnkey policies behind the required version (`AUTH_POLICY_OUTDATED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"`DatabaseError`, or credential provisioning / the allowance transaction failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"503":{"description":"API-key access to `opinion` could not be checked (fails closed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/exchanges/kalshi/enable-trading":{"post":{"operationId":"enableKalshiTrading","summary":"Connect your own Kalshi account by storing its API credentials","description":"Kalshi is the one venue where Kairos does NOT custody or provision an identity — you trade against your own Kalshi account, so you supply your own Kalshi API key and RSA private key here. The server validates the PEM, proves the credentials work by calling Kalshi's balance endpoint, then encrypts them (AES-256-GCM) and stores them.\n**You are handing over a live private key.** It is stored encrypted and used only to sign Kalshi requests on your behalf. Rotate it at Kalshi and re-run this endpoint to replace it.\nBoth PKCS#1 and PKCS#8 PEM encodings are accepted, and a key pasted as a single line with literal `\\n` escapes is normalized before parsing.\n`POST /exchanges/kalshi_offchain/enable-trading` is a deprecated alias of this route, kept for clients that are not deployed in lockstep.\n> **Error shape.** Minimal `{\"error\": \"...\", \"code\": \"...\"}` (`OrderSimpleErrorResponse`), not the structured envelope.\n**Auth & scope.** Requires `trade:execute` for `kalshi` and the mutation gate.","tags":["Onboarding"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderKalshiEnableTradingRequest"}}}},"responses":{"200":{"description":"Credentials validated against Kalshi and stored.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderKalshiEnableTradingResponse"}}}},"400":{"description":"`INVALID_USER_ID`; `MISSING_API_KEY_ID` (\"API Key ID is required\"); `INVALID_PRIVATE_KEY` (\"Invalid RSA private key. Must be a valid PEM-encoded RSA key.\"); or `INVALID_CREDENTIALS` — the key parsed but Kalshi rejected it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"`INSUFFICIENT_SCOPE` or `PLATFORM_API_ACCESS_DISABLED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"500":{"description":"`CONFIG_ERROR` (\"Internal configuration error\" / \"Encryption configuration error\"), `ENCRYPTION_ERROR` (\"Failed to encrypt credentials\"), or `DB_ERROR` (\"Failed to save credentials\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}}}}},"/exchanges/predictfun/account":{"get":{"operationId":"getPredictfunAccount","summary":"Get the caller's Predict.fun account profile","description":"Authenticates against Predict.fun with the caller's wallet and returns the `data` block of their `GET /v1/account` — display name, address, referral state and points. The upstream shape is forwarded verbatim, so new upstream fields appear without a Kairos change; treat the response as open.\n> **Error shape.** Minimal `{\"error\": \"...\", \"code\": \"...\"}` (`OrderSimpleErrorResponse`).\n**Auth.** Authentication only — this endpoint enforces **no scope** and no per-provider access check. Any authenticated credential reaches it.","tags":["Onboarding"],"x-kairos-auth":"api-key","responses":{"200":{"description":"The caller's Predict.fun profile.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderPredictfunAccountResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"404":{"description":"`NO_CREDENTIALS` — \"No Predict.fun wallet found for this user\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"500":{"description":"`INTERNAL_ERROR` — the registered executor or credential set has an unexpected type.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"503":{"description":"`NOT_CONFIGURED` — Predict.fun (or its credential provider) is not configured on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}}}}},"/polymarket/check-resolution":{"post":{"operationId":"checkPolymarketResolution","summary":"Check whether a Polymarket market has resolved","description":"Queries Polymarket's Gamma API for a market's resolution state and reports whether positions on it are redeemable. Accepts either a `0x…` condition id or a numeric Gamma market id.\nNote the path has no `/exchanges` prefix — it sits at the service root.\n**Auth.** Authentication only — this endpoint enforces **no scope** and no per-provider access check. It requires auth purely to prevent anonymous scraping of Polymarket data through Kairos.","tags":["Onboarding"],"x-kairos-auth":"api-key","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderCheckResolutionRequest"}}}},"responses":{"200":{"description":"Resolution state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderCheckResolutionResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"404":{"description":"`VALIDATION_MARKET_NOT_FOUND` — Gamma returned a non-success status, or the condition-id query matched no market.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"`INTERNAL_ERROR` — could not build the lookup URL, or could not parse Gamma's response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"502":{"description":"`EXCHANGE_POLYMARKET_API_ERROR` — the Gamma request itself failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/exchanges/hyperliquid/withdraw/prepare":{"post":{"operationId":"prepareHyperliquidWithdraw","summary":"Build the typed data for a Hyperliquid withdrawal (step 1 of 2)","description":"Builds the EIP-712 `withdraw3` payload for a USDC withdrawal from Hyperliquid to an Arbitrum address. **Kairos never signs a withdrawal** — your main wallet does, and this endpoint only hands you the bytes.\n**This call is pure.** It writes nothing, reserves nothing, and has no side effects. Never calling `POST /exchanges/hyperliquid/withdraw` afterwards has zero consequence.\n**`destination` defaults to `wallet_address`.** Omit it and you withdraw to yourself. If you set it, be certain: Kairos validates only the *amount*, never the destination address, and a signed withdrawal to a wrong address is irreversible.\n**No server-side binding between prepare and submit.** The submit endpoint accepts any well-formed `{amount, time, destination, signature}` — correctness rests entirely on Hyperliquid re-verifying your signature. Prepare is a convenience, not a control.\n**Auth & scope.** Requires `trade:execute` for `hyperliquid`, wallet ownership, and the mutation gate. Not rate limited.","tags":["Hyperliquid"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderHyperliquidWithdrawPrepareRequest"}}}},"responses":{"200":{"description":"Typed data to sign, plus the `time` nonce to echo back.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderHyperliquidWithdrawPrepareResponse"}}}},"400":{"description":"`ValidationInvalidOrder` — invalid user id, or `Invalid withdraw amount: …` (not a plain positive decimal).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"\"This API key lacks the trade:execute scope\", API-key access to `hyperliquid` disabled, or \"Wallet not found or does not belong to you\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"`DatabaseError`, or `InternalError` — \"Hyperliquid signing misconfigured\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"503":{"description":"API-key access to `hyperliquid` could not be checked (fails closed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/exchanges/hyperliquid/withdraw":{"post":{"operationId":"submitHyperliquidWithdraw","summary":"Submit a signed Hyperliquid withdrawal (step 2 of 2)","description":"Forwards your main wallet's signed `withdraw3` action to Hyperliquid. Kairos verifies the signature parses and then passes it through; the venue re-verifies it covers `destination`, `amount` and `time`, so a field altered after signing is rejected upstream.\n**Irreversible.** A withdrawal that Hyperliquid accepts cannot be recalled. Kairos does not validate the destination address.\n**No Kairos-side idempotency.** There is no in-flight tracking and no dedupe: calling this twice with the same `time` sends the same signed action twice, and the ONLY protection against a double withdrawal is Hyperliquid rejecting the replayed nonce. Do not build a blind retry loop on this endpoint.\n**A `400` conflates two very different outcomes.** `Hyperliquid rejected withdrawal: …` is returned both for a genuine venue rejection AND for an HTTP/network failure reaching the venue — the underlying error type collapses them. On that error you cannot tell whether the withdrawal landed; query Hyperliquid directly before retrying.\nHyperliquid's own withdrawal minimum and flat fee are enforced venue-side and surface through that same `400`.\n**Auth & scope.** Requires `trade:execute` for `hyperliquid`, wallet ownership, and the mutation gate. Not rate limited.","tags":["Hyperliquid"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderHyperliquidWithdrawRequest"}}}},"responses":{"200":{"description":"The withdrawal was submitted to Hyperliquid. Submission only — not a settlement confirmation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderHyperliquidActionResponse"}}}},"400":{"description":"`ValidationInvalidOrder` — invalid user id, `Invalid withdraw amount: …`, or `Invalid withdrawal signature: …`. **Also** `InternalError` with message `Hyperliquid rejected withdrawal: …`, which covers venue rejection and network failure indistinguishably.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Missing scope, provider access disabled, or wallet not owned by the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"`DatabaseError`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"503":{"description":"API-key access to `hyperliquid` could not be checked (fails closed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/exchanges/hyperliquid/transfer/prepare":{"post":{"operationId":"prepareHyperliquidTransfer","summary":"Build the typed data for a Hyperliquid spot↔perp transfer (step 1 of 2)","description":"Builds the EIP-712 `usdClassTransfer` payload for moving USDC between your own Hyperliquid spot and perp balances. **Funds never leave your account** — there is no destination and nothing to mis-address.\nYour MAIN wallet must sign: Hyperliquid forbids agent wallets from class transfers, so a Kairos-held agent key cannot do this for you.\n**This call is pure** — same properties as the withdraw prepare: no writes, no reservation, no binding to the submit step.\n**Auth & scope.** Requires `trade:execute` for `hyperliquid`, wallet ownership, and the mutation gate. Not rate limited.","tags":["Hyperliquid"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderHyperliquidTransferPrepareRequest"}}}},"responses":{"200":{"description":"Typed data to sign, plus the `time` nonce to echo back.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderHyperliquidTransferPrepareResponse"}}}},"400":{"description":"`ValidationInvalidOrder` — invalid user id, or `Invalid withdraw amount: …` (the withdraw-worded message is reused for transfers).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Missing scope, provider access disabled, or wallet not owned by the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"`DatabaseError`, or `InternalError` — \"Hyperliquid signing misconfigured\".","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"503":{"description":"API-key access to `hyperliquid` could not be checked (fails closed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/exchanges/hyperliquid/transfer":{"post":{"operationId":"submitHyperliquidTransfer","summary":"Submit a signed Hyperliquid spot↔perp transfer (step 2 of 2)","description":"Forwards your signed `usdClassTransfer` to Hyperliquid. Because the move is internal to your own account, the blast radius is far smaller than a withdrawal — but the same mechanics apply: no Kairos-side idempotency, `time` doubles as the nonce, and replay protection is Hyperliquid's alone.\nThe `400 Hyperliquid rejected transfer: …` conflates venue rejection with network failure, exactly as on withdraw.\n**Auth & scope.** Requires `trade:execute` for `hyperliquid`, wallet ownership, and the mutation gate. Not rate limited.","tags":["Hyperliquid"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderHyperliquidTransferRequest"}}}},"responses":{"200":{"description":"The transfer was submitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderHyperliquidActionResponse"}}}},"400":{"description":"`ValidationInvalidOrder` — invalid user id, `Invalid withdraw amount: …`, or `Invalid transfer signature: …`. **Also** `InternalError` with message `Hyperliquid rejected transfer: …` (venue rejection or network failure, indistinguishable).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Missing scope, provider access disabled, or wallet not owned by the caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"`DatabaseError`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"503":{"description":"API-key access to `hyperliquid` could not be checked (fails closed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/exchanges/polymarket/deposit-wallet/onboard":{"post":{"operationId":"onboardDepositWallet","summary":"Deploy the caller's Polymarket deposit wallet and set its trading approvals","description":"Creates the user's **deposit wallet** — a per-user ERC-1967 proxy on Polygon, deployed through Polymarket's relayer at a deterministic CREATE2 address derived from the owner EOA. Two relayer operations run: the deployment, then a signed batch setting the CTF/collateral approvals the venue needs.\n**Idempotent on the deployment leg.** Re-submitting for an already-deployed owner is treated as a no-op success and comes back with `deploy_tx_id: null`.\n**It does not move user funds** — it deploys a contract and grants approvals. The approval batch is signed server-side under the user's delegated key; the batch deadline window is one hour, and each relayer transaction is waited on for up to 120 s.\nA successful response means the submitted approval batch has confirmed. `batch_tx_id` is empty when no batch was submitted.\n**Auth & scope.** Requires `trade:execute` for `polymarket`, identity ownership, and the mutation gate.","tags":["Deposit wallet"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderDepositWalletIdentityRequest"}}}},"responses":{"200":{"description":"Wallet deployed (or already present) and approvals submitted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderDepositWalletOnboardResponse"}}}},"400":{"description":"`ValidationInvalidOrder` — `user_id` is not a valid UUID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"`AuthInsufficientScope` — \"This API key lacks the trade:execute scope\"; or `AuthIdentityMismatch` — a `user_id` / `turnkey_org_id` in the body that is not the authenticated account (omit them to avoid this). Note that the provider-access check on this endpoint returns a GENERIC body (see the description of `OrderErrorResponse`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"`DatabaseError` — ownership could not be verified; or `InternalError` — the relayer client is unavailable or the onboarding itself failed. The underlying relayer reason is logged server-side and NOT returned.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"503":{"description":"API-key access to `polymarket` could not be checked (fails closed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/exchanges/polymarket/imported/relay-info":{"post":{"operationId":"getImportedRelayInfo","summary":"Read the relayer nonce (and GSN relay) for an imported Polymarket wallet","description":"The imported-wallet analogue of the deposit-wallet nonce endpoint: returns the relayer nonce and, for a legacy ProxyWallet (`sig_type: proxy`), the GSN relay address needed to construct the digest the browser signs. For a Gnosis Safe (`sig_type: safe`) `relay` is always `null`.\nFetched server-side purely because Polymarket's relayer is CORS-blocked from browsers. The response is public information; the ownership check is belt-and-braces, not a secrecy boundary.\n> **Error shape.** Most 4xx/5xx here have an **empty `text/plain` body**; the `502` cases carry a plain-text reason (`relay-payload fetch failed: …` / `nonce fetch failed: …`). Scope and identity failures return the structured envelope.\n**Auth & scope.** Requires `trade:execute` for `polymarket` and identity + wallet ownership. No mutation gate, so a bare session JWT reaches it.","tags":["Deposit wallet"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderImportedRelayInfoRequest"}}}},"responses":{"200":{"description":"Relayer nonce, plus the GSN relay for `proxy`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderImportedRelayInfoResponse"}}}},"400":{"description":"Malformed or unparseable `owner_address`. Empty body (`text/plain`). An identity mismatch instead returns the structured `AuthIdentityMismatch` body."},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Missing `trade:execute` scope, API-key access to `polymarket` disabled, or an identity mismatch. Structured body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"The relayer client is unavailable. Empty body (`text/plain`)."},"502":{"description":"Plain-text `relay-payload fetch failed: <reason>` (proxy) or `nonce fetch failed: <reason>` (safe)."},"503":{"description":"API-key access to `polymarket` could not be checked (fails closed). Structured body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/v2/onchain/intent":{"post":{"operationId":"createOnchainIntent","summary":"Build pinned unsigned on-chain transactions for self-signing (step 1 of 2)","description":"The on-chain counterpart to `POST /v2/orders/intent`, for the operations an order cannot do off-chain: one-time approvals, redemptions of resolved positions, CTF split/merge, and unwrapping wrapped collateral. Kairos builds the exact calldata, pins it under a single-use `payload_id` (300 s TTL), and hands you the EIP-155 signing digest for each transaction. **You** sign with your own EOA and **you** pay the gas; Kairos only broadcasts what you signed via `POST /v2/onchain/submit`.\n**Polygon / Polymarket only.** `chain_id` is pinned to `137`; there is no `provider` field.\n**No RPC at build time.** You supply `nonce` and `gas_price_wei`; the server does not fetch a nonce, quote gas, or preflight your balance. A stale nonce or an under-priced transaction surfaces at broadcast, not here.\n**Multi-leg.** `op=approvals` returns several transactions; sign each `signing_digest_hex` raw (no EIP-191 prefix) and submit the signatures in the same order.\n**Auth & scope.** Requires `trade:execute` for `polymarket` AND the `User.executionFastlaneEnabled` allowlist — the same gate as the order lane. `owner_address` must be a wallet registered to the authenticated caller. There is no service-token, admission, circuit-breaker or rate-limit gate on this route.","tags":["Orders"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderOnchainIntentRequest"}}}},"responses":{"200":{"description":"Pinned unsigned transactions plus the digests to sign.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderOnchainIntentResponse"}}}},"400":{"description":"Exact `error` strings: `invalid owner_address`; `owner_address is not a registered wallet for\nthis user`; `invalid gas_price_wei (expected a wei decimal string)`; `gas_price_wei must be > 0`;\n`condition_id is required for op=redeem`; `condition_id is required for op=<op>`;\n`amount (wei) is required for op=<op>`; `invalid amount (expected a wei decimal string)`;\n`amount must be > 0`; `wcol_amount (wei) is required for op=unwrap_wcol`;\n`invalid wcol_amount (expected a wei decimal string)`; `wcol_amount must be > 0`;\n`unsupported op: <other>`; or the calldata builder's own message.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"`API key missing trade:execute scope`; `API-key access to polymarket is disabled`; `external-signing execution is not enabled for this account`; `external-signing authorization check failed`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"500":{"description":"`internal server error` — failed to serialize or store the intent, or a `payload_id` collision.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"503":{"description":"`API-key access to polymarket is unavailable` — the API-key access cache read failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}}}}},"/v2/onchain/submit":{"post":{"operationId":"submitOnchainSigned","summary":"Broadcast your externally-signed on-chain transactions (step 2 of 2)","description":"Hand back the `payload_id` and one signature per transaction, in the order `POST /v2/onchain/intent` returned them. The server claims the stored intent atomically (single-use Redis `GETDEL` — a replay gets `400`), re-verifies each signature recovers to the declared owner AND to a wallet registered to you, reassembles the signed transactions, and broadcasts them **in nonce order, waiting up to 90 s for each to confirm** before sending the next.\n**Partial failure is possible and is reported.** If leg `i` fails to broadcast or revert-checks, the response is a `502` whose message lists the transaction hashes already mined (`already broadcast: [...]`). Earlier legs stay on chain; re-intent only the remainder.\n**Auth & scope.** Identical gate to `POST /v2/onchain/intent`.","tags":["Orders"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderOnchainSubmitRequest"}}}},"responses":{"200":{"description":"All transactions broadcast and confirmed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderOnchainSubmitResponse"}}}},"400":{"description":"Exact `error` strings: `payload_id not found, expired, or already claimed`;\n`payload_id does not belong to authenticated user`; `payload_id expired`;\n`expected N signature(s), got M`; `tx <i>: invalid signature hex`;\n`tx <i>: <verification error>` (e.g. wrong length, or the signature does not recover to the\ndeclared owner); `tx <i>: signer is not a registered wallet`; `tx <i>: <reassembly error>`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Same set as `POST /v2/onchain/intent`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"500":{"description":"`internal server error` — Redis claim failure, the stored intent no longer deserializes, or the Polygon RPC is not configured on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"502":{"description":"`tx <i> failed to broadcast: <error> (already broadcast: [...])` — earlier transactions in the batch may already be mined.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}},"503":{"description":"`API-key access to polymarket is unavailable`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderSimpleErrorResponse"}}}}}}},"/exchanges/{exchange_id}/ctf/split":{"post":{"operationId":"ctfSplit","summary":"Split collateral into a complete outcome-token set","tags":["CTF"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","description":"Converts `amount` of collateral into `amount` YES **and** `amount` NO tokens on-chain, without going through the order book. Does not open a market position — it mints equal tokens on every outcome; both legs are recorded as synthetic BUY fills at price 0.5 so history and PnL stay consistent. NegRisk (multi-outcome) markets are detected from the conditionId and routed through the NegRiskAdapter automatically. Transactions are custodially signed; deposit-wallet users execute gaslessly via the relayer. Identity (`user_id`/`turnkey_org_id`/`wallet_address`) is resolved server-side from the authenticated caller — body fields are optional and must match when present. Because this signs under the user's delegated key it is additionally gated on the installed Turnkey policy version (`403 AUTH_POLICY_OUTDATED` if stale) and, like the other mutation endpoints, on the service-token/CSRF/API-key check that the API-key triple satisfies automatically. Failures return the structured `OrderErrorResponse` envelope — match on `error_details.code`.","parameters":[{"name":"exchange_id","in":"path","required":true,"description":"Venue. Split/merge require an on-chain CTF (polymarket, predictfun). Kalshi settles off-exchange — redeem only.","schema":{"type":"string","enum":["polymarket","predictfun","kalshi","opinion"]},"example":"polymarket"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderCtfSplitMergeRequest"},"example":{"condition_id":"0x1234abcd","market_id":"570362","amount":100}}}},"responses":{"200":{"description":"Split executed on-chain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderCtfSplitResponse"}}}},"400":{"description":"Invalid `condition_id`, non-positive `amount`, an exchange that doesn't support the action (e.g. split/merge on Kalshi → `EXCHANGE_UNSUPPORTED`), or insufficient collateral (split) / outcome tokens (merge) → `FUNDS_INSUFFICIENT_USDC` or `FUNDS_INSUFFICIENT_BALANCE`. An insufficient-funds reject on this service is a `400`; there is no `402` anywhere in this API.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Identity fields conflict with the authenticated caller (`AUTH_IDENTITY_MISMATCH`), wallet not owned by the caller, missing `trade:execute` scope, API-key access to this provider disabled, or installed Turnkey policies behind the version this path requires (`AUTH_POLICY_OUTDATED`, action `update_policies` — split, merge and redeem all enforce it).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"404":{"description":"`VALIDATION_MARKET_NOT_FOUND` — the condition/market could not be resolved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"`INTERNAL_ERROR` / `DATABASE_ERROR` / `SIGNATURE_ERROR` — bookkeeping or custodial signing failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"502":{"description":"On-chain submission or the RPC call failed (`NETWORK_ERROR` / `EXCHANGE_ERROR`) — generally safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"504":{"description":"`NETWORK_TIMEOUT` — the on-chain call timed out; the transaction may still land.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/exchanges/{exchange_id}/ctf/merge":{"post":{"operationId":"ctfMerge","summary":"Merge a complete outcome-token set back into collateral","tags":["CTF"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","description":"Burns `amount` YES **and** `amount` NO tokens and returns `amount` collateral — recover capital from matched inventory without waiting for resolution. Requires holding at least `amount` of every outcome token. Recorded as two synthetic SELL fills at price 0.5. NegRisk routing, custodial signing, the Turnkey policy-version gate, the service-token/CSRF/API-key mutation gate, and server-side identity resolution behave exactly as on split.","parameters":[{"name":"exchange_id","in":"path","required":true,"description":"Venue. Split/merge require an on-chain CTF (polymarket, predictfun). Kalshi settles off-exchange — redeem only.","schema":{"type":"string","enum":["polymarket","predictfun","kalshi","opinion"]},"example":"polymarket"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderCtfSplitMergeRequest"},"example":{"condition_id":"0x1234abcd","market_id":"570362","amount":290}}}},"responses":{"200":{"description":"Merge executed on-chain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderCtfMergeResponse"}}}},"400":{"description":"Invalid `condition_id`, non-positive `amount`, an exchange that doesn't support the action (e.g. split/merge on Kalshi → `EXCHANGE_UNSUPPORTED`), or insufficient collateral (split) / outcome tokens (merge) → `FUNDS_INSUFFICIENT_USDC` or `FUNDS_INSUFFICIENT_BALANCE`. An insufficient-funds reject on this service is a `400`; there is no `402` anywhere in this API.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Identity fields conflict with the authenticated caller (`AUTH_IDENTITY_MISMATCH`), wallet not owned by the caller, missing `trade:execute` scope, API-key access to this provider disabled, or installed Turnkey policies behind the version this path requires (`AUTH_POLICY_OUTDATED`, action `update_policies` — split, merge and redeem all enforce it).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"404":{"description":"`VALIDATION_MARKET_NOT_FOUND` — the condition/market could not be resolved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"`INTERNAL_ERROR` / `DATABASE_ERROR` / `SIGNATURE_ERROR` — bookkeeping or custodial signing failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"502":{"description":"On-chain submission or the RPC call failed (`NETWORK_ERROR` / `EXCHANGE_ERROR`) — generally safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}},"/exchanges/{exchange_id}/redeem":{"post":{"operationId":"ctfRedeem","summary":"Redeem winning outcome tokens after resolution","tags":["CTF"],"x-kairos-auth":"api-key","x-kairos-scope":"trade:execute","description":"After a market resolves on-chain, converts the winning outcome tokens into collateral. Only the winning side is needed (losing shares are worthless). NegRisk redeems unwrap wrapped collateral back to the base asset automatically. `db_update_failed: true` in the response means the on-chain redeem SUCCEEDED but the position-state bookkeeping write failed — funds are safe; retry the call to fix the bookkeeping. Failures return the structured `OrderErrorResponse` envelope — match on `error_details.code`.","parameters":[{"name":"exchange_id","in":"path","required":true,"description":"Venue. Split/merge require an on-chain CTF (polymarket, predictfun). Kalshi settles off-exchange — redeem only.","schema":{"type":"string","enum":["polymarket","predictfun","kalshi","opinion"]},"example":"polymarket"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderCtfRedeemRequest"},"example":{"condition_id":"0x1234abcd","market_id":"570362"}}}},"responses":{"200":{"description":"Redeem executed on-chain.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderCtfRedeemResponse"}}}},"400":{"description":"Invalid `condition_id`, non-positive `amount`, an exchange that doesn't support the action (e.g. split/merge on Kalshi → `EXCHANGE_UNSUPPORTED`), or insufficient collateral (split) / outcome tokens (merge) → `FUNDS_INSUFFICIENT_USDC` or `FUNDS_INSUFFICIENT_BALANCE`. An insufficient-funds reject on this service is a `400`; there is no `402` anywhere in this API.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"401":{"$ref":"#/components/responses/ExecUnauthorized"},"403":{"description":"Identity fields conflict with the authenticated caller (`AUTH_IDENTITY_MISMATCH`), wallet not owned by the caller, missing `trade:execute` scope, API-key access to this provider disabled, or installed Turnkey policies behind the version this path requires (`AUTH_POLICY_OUTDATED`, action `update_policies` — split, merge and redeem all enforce it).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"404":{"description":"`VALIDATION_MARKET_NOT_FOUND` — the condition/market could not be resolved.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"409":{"description":"Venue shows resolved but on-chain payouts are still all zero (`VALIDATION_MARKET_NOT_SETTLED_ON_CHAIN`). Transient — wait for the dispute window and retry; primary action is `retry`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"500":{"description":"Unexpected fault, or a sponsored redeem that needs manual review (`INTERNAL_ERROR`; may include `contact_support`). Do not blindly retry wedged sponsored requests.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}},"502":{"description":"On-chain submission or the RPC call failed (`NETWORK_ERROR` / `EXCHANGE_ERROR`) — generally safe to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderErrorResponse"}}}}}}}}}},{"id":"market-data-api","title":"Kairos Market Data API","baseUrls":["https://md.kairos.trade","https://staging-md.kairos.trade"],"spec":{"openapi":"3.1.0","x-kairos-anonymous":true,"info":{"title":"Kairos Market Data API","version":"1.2.0","summary":"Read-only prediction and perpetual market data across venues.","description":"The Kairos Market Data API provides read-only access\nto normalized prediction-market data across every venue Kairos integrates:\nOHLCV candles, the trade tape, market metadata, resolution lifecycles, and\nlatest mark prices.\n\n## No signup required\n\nEvery endpoint works **without credentials** on the anonymous free tier,\nrate-limited per source IP (120 light units/min, 20 heavy units/min). Try any\nrequest from the interactive docs at\n[app.kairos.trade/docs/market-data](https://app.kairos.trade/docs/market-data)\nor straight from curl. For production budgets (1,200 light and 150 heavy\nunits/min by default, raisable per credential), request an API key from the\nKairos team.\n\n## Authentication\n\nThree modes, resolved per request:\n\n- **Anonymous** — no credential headers at all. Free tier, IP-keyed limits.\n- **API key** — `X-Client-Id` + `X-Api-Key` + `X-Api-Secret` headers\n  (all three; partial header sets are rejected with 401 rather than\n  degrading to anonymous).\n- **First-party session** — `Authorization: Bearer` JWT (Kairos web app\n  sessions only; not offered to API consumers).\n\nResolution order is fixed: an `Authorization` header takes the bearer\npath, any of the three API-key headers takes the API-key path, and only\na request carrying neither is eligible for the anonymous tier.\n\nOn the API-key path, an unreachable credential store surfaces on **any**\nendpoint as `500` with `code: internal` and the message\n`authentication backend unavailable`. A source IP outside a credential's\nwhitelist is `403 ip_not_whitelisted`; that is the only 403 this service\nemits.\n\n## Rate limiting\n\nWeighted sliding-window budgets over one minute, in **units**, across two\nindependent buckets: light (candles, metadata, resolutions, trade\nmetrics) and heavy (trade history, marks, perpetual snapshots). Most\nrequests cost 1 unit; larger requests may consume more of your quota —\neach operation's `x-kairos-rate-limit` states its exact cost model.\nEvery admitted response carries `X-RateLimit-Bucket`, `X-RateLimit-Tier`,\n`X-RateLimit-Remaining`, and `X-RateLimit-Reset`; 429s add `Retry-After`.\n\nA separate per-IP admission gate runs *before* authentication to keep a\nflood off shared infrastructure. It also answers 429 `rate_limited`, but\nwith `Retry-After: 60` and no `X-RateLimit-*` headers.\n\nRedis backs the weighted limiter and it fails **closed**: if it is\nunreachable the request is rejected with 503 `rate_limiter_unavailable`\nrather than let through unmetered.\n\n## Conventions\n\n- **Prediction-market prices** are on the 0–100 scale (implied probability\n  × 100) in the prediction candle, trade, and mark endpoints. Prediction\n  USD notional = `size × price / 100`.\n- **Perpetual prices are direct venue prices**, not probabilities and not\n  0–100 values. Perpetual quantities retain their declared native unit\n  (`base_asset` or `contracts`). Never divide a perpetual price by 100 or\n  compute contract notional without the instrument's authoritative contract\n  multiplier; the snapshot does not synthesize a cross-venue notional.\n- **Hyperliquid is two products on one venue.** Its HIP-4 outcome markets\n  are prediction markets, read with `provider=hyperliquid` on the\n  prediction endpoints; its perpetuals are bare coins read under\n  `/v1/perpetuals/hyperliquid/{instrument}/snapshot`, where the instrument\n  is the canonical id `hl-mainnet-<symbol>-<quote>` (e.g.\n  `hl-mainnet-btc-usdt`). Different id schemes, different price semantics;\n  they are never interchangeable.\n- **Errors** all use one envelope:\n  `{\"error\": {\"code\": \"...\", \"message\": \"...\"}}`.\n- **Providers** are case-insensitive; `kalshi_offchain` aliases `kalshi`,\n  `dome` aliases `polymarket`, and `opinion` is resolvable for historic\n  reads only.\n","contact":{"name":"Kairos","url":"https://app.kairos.trade/docs/market-data"},"termsOfService":"https://kairos.trade/terms"},"servers":[{"url":"https://md.kairos.trade","description":"Production"},{"url":"https://staging-md.kairos.trade","description":"Staging"}],"security":[{},{"apiKeyClientId":[],"apiKeyKey":[],"apiKeySecret":[]}],"tags":[{"name":"Candles","description":"OHLCV candle series (1s → 1d), JSON or compact columnar binary."},{"name":"Trades","description":"Trade tape and aggregate volume metrics."},{"name":"Markets","description":"Market metadata, batch lookup, identifier resolution, and enumeration."},{"name":"Resolutions","description":"Resolution outcomes and UMA-style lifecycle state/timelines."},{"name":"Marks","description":"Latest mark (last trade price) per outcome token."},{"name":"Perpetuals","description":"Live perpetual books, trades, candles, funding, and market state. Beta."},{"name":"Status","description":"Health and readiness probes."}],"paths":{"/health":{"get":{"operationId":"getHealth","x-kairos-auth":"public","x-kairos-rate-limit":"none — registered directly on the mux, outside the auth and rate-limit middleware chain","summary":"Liveness probe","tags":["Status"],"security":[],"description":"Trivially cheap liveness check used by the load balancer. Touches no dependencies and is never rate-limited.\n","responses":{"200":{"description":"Service is up.","content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","example":"ok"}}}}}}}}},"/ready":{"get":{"operationId":"getReady","x-kairos-auth":"public","x-kairos-rate-limit":"none — registered directly on the mux, outside the auth and rate-limit middleware chain","summary":"Readiness probe","tags":["Status"],"security":[],"description":"Probes the two dependencies in order — ClickHouse (`SELECT 1`, 3s deadline) then Redis (`PING`) — and returns 503 naming the first one that fails.\n","responses":{"200":{"description":"All dependencies reachable.","content":{"application/json":{"schema":{"type":"object","required":["ready"],"properties":{"ready":{"type":"boolean","example":true}}}}}},"503":{"description":"A dependency is unreachable.","content":{"application/json":{"schema":{"type":"object","required":["ready","failing"],"properties":{"ready":{"type":"boolean","example":false},"failing":{"type":"string","description":"Name of the unavailable dependency.","enum":["clickhouse","redis"],"example":"clickhouse"}}}}}}}}},"/v1/markets/{provider}/{market_id}":{"get":{"operationId":"getMarket","x-kairos-auth":"public","x-kairos-rate-limit":"1 light unit per request","summary":"Get one market's metadata","tags":["Markets"],"description":"Returns the full metadata document for a single market. Rate-limit bucket: LIGHT.\n\nProvider is resolved case-insensitively against the registry (aliases such as `kalshi_offchain` → `kalshi` and `dome` → `polymarket` are accepted and normalized before the lookup).\n\nResponse is ETagged (strong ETag) with a `Cache-Control` header; a matching `If-None-Match` returns 304. Responses ≥1KB are gzip-compressed when the client sends `Accept-Encoding: gzip`.","parameters":[{"name":"provider","in":"path","required":true,"description":"Venue identifier. Case-insensitive; aliases are normalized (`kalshi_offchain`→`kalshi`, `dome`→`polymarket`). Unknown values are rejected with 400.","schema":{"type":"string","enum":["kalshi","polymarket","opinion","predictfun","hyperliquid","kalshi_offchain","dome"]},"example":"polymarket"},{"name":"market_id","in":"path","required":true,"description":"Venue-specific market identifier (e.g. a Kalshi ticker, or a Polymarket/predict.fun numeric market id or condition id).","schema":{"type":"string"},"example":"1897040"}],"responses":{"200":{"description":"Market metadata document.","headers":{"ETag":{"schema":{"type":"string"}},"Cache-Control":{"schema":{"type":"string","example":"public, max-age=30"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Market"},"example":{"exchange_id":"polymarket","market_id":"1897040","condition_id":"0xabc123...","event_id":"evt-4521","title":"Will BTC close above $120k on July 31?","neg_risk":false,"tick_size":0.01,"taker_base_fee_bps":200,"fees_enabled":true,"category":"Crypto","group_slug":"btc-price-2026","fee_type":"standard","status":"active","image":"https://cdn.kairos.trade/markets/1897040.png","icon":"https://cdn.kairos.trade/markets/1897040-icon.png","end_date":"2026-07-31T23:59:59Z","open_time":"2026-01-01T00:00:00Z","outcomes":[{"outcome":"Yes","normalized_outcome":"yes","token_id":"18812649149814341758733697580460697418474693998558159483117"},{"outcome":"No","normalized_outcome":"no","token_id":"88123409981238091823740918237409182734091823740918237409182"}],"raw":{}}}}},"304":{"description":"Not Modified — `If-None-Match` matched the current ETag."},"400":{"description":"Unknown provider.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"invalid_request","message":"unknown provider \"coinbase\""}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/IPNotWhitelisted"},"404":{"description":"Market not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"not_found","message":"market not found"}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"`internal` / `authentication backend unavailable` — the API-key path could not reach the credential store.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"internal","message":"authentication backend unavailable"}}}}},"502":{"description":"Market lookup failed upstream for a reason other than not-found/cold/disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"upstream","message":"metadata cache request failed"}}}}},"503":{"description":"Either the market data source is still warming up (`cache_cold`, with a `Retry-After: 5` header — retry shortly), not configured for this deployment (`unavailable`), or the rate limiter is unreachable (`rate_limiter_unavailable`, fails closed).","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Present only for `cache_cold`."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"cache_cold","message":"metadata cache warming up, retry shortly"}}}}}},"x-kairos-bucket":"light"}},"/v1/markets/batch":{"post":{"operationId":"batchGetMarkets","x-kairos-auth":"public","x-kairos-rate-limit":"1 light unit per request, regardless of batch size","summary":"Fetch multiple markets by id, one provider at a time","tags":["Markets"],"description":"Fetches metadata for one provider and up to 200 market ids in a single call. Rate-limit bucket: LIGHT, flat cost of 1 unit regardless of batch size (unlike `/v1/market-identifiers/resolve`, this endpoint does NOT charge per-item). Not cached (`Cache-Control: no-store`) — every call returns live data.\n\nRequest body is capped at 1 MiB, and `market_ids` is capped at 200 entries per request (`market_ids exceeds maximum 200`).\n\nIds that cannot be found are omitted from `markets` and listed in `misses`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketBatchRequest"},"example":{"provider":"polymarket","market_ids":["1897040","1897041","does-not-exist"]}}}},"responses":{"200":{"description":"Batch lookup result, keyed by market_id.","headers":{"Cache-Control":{"schema":{"type":"string","example":"no-store"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketBatchResponse"},"example":{"exchange_id":"polymarket","markets":{"1897040":{"exchange_id":"polymarket","market_id":"1897040","condition_id":"0xabc123...","event_id":"evt-4521","title":"Will BTC close above $120k on July 31?","neg_risk":false,"tick_size":0.01,"outcomes":[{"outcome":"Yes","normalized_outcome":"yes","token_id":"18812649149814341758733697580460697418474693998558159483117"}],"raw":{}}},"misses":["does-not-exist"]}}}},"400":{"description":"Unreadable/invalid JSON body, unknown provider, missing `market_ids`, or `market_ids` exceeds the maximum of 200.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"too_many":{"value":{"error":{"code":"invalid_request","message":"market_ids exceeds maximum 200"}}},"empty":{"value":{"error":{"code":"invalid_request","message":"market_ids is required"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/IPNotWhitelisted"},"404":{"description":"The upstream metadata cache answered the batch lookup with 404. Individual ids that are simply absent come back in `misses` with a 200 instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"not_found","message":"market not found"}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"`internal` / `authentication backend unavailable` — the API-key path could not reach the credential store.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"internal","message":"authentication backend unavailable"}}}}},"502":{"description":"The metadata cache answered with an unexpected status (`upstream` / `metadata cache request failed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"upstream","message":"metadata cache request failed"}}}}},"503":{"description":"`cache_cold` — the metadata cache is still warming (with `Retry-After: 5`); `unavailable` — no metadata cache is configured for this deployment; or `rate_limiter_unavailable`.","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Present only for `cache_cold`."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"x-kairos-bucket":"light"}},"/v1/market-identifiers/resolve":{"post":{"operationId":"resolveMarketIdentifiers","x-kairos-auth":"public","x-kairos-rate-limit":"1 light unit per submitted item — admitted at 1 unit, then `len(requests) - 1` charged as a deferred charge","summary":"Resolve venue-specific identifiers to canonical Kairos market ids","tags":["Markets"],"description":"Maps up to 200 venue-specific identifiers — market-scoped (a venue's market id, ticker, or condition-like identifier) or outcome-scoped (an outcome/token id) — onto Kairos's canonical `market_id` for that provider. Requests are grouped by (provider, scope) for efficient batch resolution. Not cached (`Cache-Control: no-store`).\n\nRate-limit bucket: LIGHT, but with a distinct cost model from the other batch endpoints — the request is admitted at a base cost of 1 unit, then the service charges the FULL request-item count (`len(requests) - 1` additional units) as a deferred charge immediately after the body is parsed and size-validated, before any per-item validation runs. This means a batch of N requests always costs N light units even if individual items subsequently fail validation (e.g. a bad `scope`) and the call returns 400.\n\nUnresolved identifiers are reported with `found: false` and `market_id: null` rather than causing the whole call to fail.\n\n**Id spaces.** `scope: market` matches the venue's market identity — a Kalshi ticker, a Polymarket Gamma numeric market id, or a `0x…` condition id. `scope: outcome` matches an on-chain outcome/token id only (the decimal ERC1155 string), never a market id; submitting a market id with `scope: outcome` returns `found: false`. Resolution always returns the canonical `market_id`; it never returns outcome token ids. To go from a market discovered via the Data API's `/search/markets` to its outcome tokens, read `token_ids`/`outcomes` on the search result (or call the Data API's `POST /markets/details`), then feed those token ids to `/v1/synthetics` and `/v1/candles`.\n\nThe request body is capped at 1 MiB.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketIdentifierResolveRequestBody"},"example":{"requests":[{"provider":"polymarket","identifier":"18812649149814341758733697580460697418474693998558159483117","scope":"outcome"},{"provider":"kalshi","identifier":"KXBTC-26JUL","scope":"market"}]}}}},"responses":{"200":{"description":"Per-item resolution results, in request order.","headers":{"Cache-Control":{"schema":{"type":"string","example":"no-store"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketIdentifierResolveResponse"},"example":{"results":[{"provider":"polymarket","identifier":"18812649149814341758733697580460697418474693998558159483117","scope":"outcome","found":true,"market_id":"1897040"},{"provider":"kalshi","identifier":"KXBTC-26JUL","scope":"market","found":true,"market_id":"KXBTC-26JUL"}]}}}},"400":{"description":"Unreadable/invalid JSON body, empty `requests`, `requests` exceeds 200, an item's `provider` is unknown, an item's `identifier` is empty after trimming, or an item's `scope` is not `market` or `outcome`. Per-item messages include the offending index (e.g. `requests[2].scope must be market or outcome`); the unknown-provider message does not (it reads `unknown provider \"…\"`). Validation stops at the first bad item.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"invalid_request","message":"requests[0].scope must be market or outcome"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/IPNotWhitelisted"},"404":{"description":"The upstream metadata cache answered one of the (provider, scope) group lookups with 404. Identifiers that are merely unresolvable come back as `found: false` with a 200.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"not_found","message":"market not found"}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Response encoding failed (internal error, not upstream).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"internal","message":"market identifier response encoding failed"}}}}},"502":{"description":"A (provider, scope) group lookup failed against the metadata cache — an unexpected upstream status, an undecodable body, or a resolved entry missing its `market_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"upstream","message":"metadata cache request failed"}}}}},"503":{"description":"`cache_cold` (metadata cache still warming, `Retry-After: 5`), `unavailable` (no metadata cache configured), or `rate_limiter_unavailable`.","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Present only for `cache_cold`."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"x-kairos-bucket":"light"}},"/v1/markets":{"get":{"operationId":"listMarkets","x-kairos-auth":"public","x-kairos-rate-limit":"1 light unit per request","summary":"Paginate active markets for one provider","tags":["Markets"],"description":"Enumerates active markets for a single provider, cursor-paginated. Rate-limit bucket: LIGHT.\n\nThe response is ETagged (strong ETag) with a `Cache-Control` header; a matching `If-None-Match` returns 304.\n\nReturns 503 (`cache_cold`) until the active-market listing has been populated for the requested provider at least once.","parameters":[{"name":"provider","in":"query","required":true,"description":"Venue identifier. Case-insensitive; unknown values are rejected with 400.","schema":{"type":"string","enum":["kalshi","polymarket","opinion","predictfun","hyperliquid","kalshi_offchain","dome"]},"example":"kalshi"},{"name":"limit","in":"query","required":false,"description":"Page size. Must be an integer in [1, 250].","schema":{"type":"integer","minimum":1,"maximum":250,"default":100},"example":100},{"name":"cursor","in":"query","required":false,"description":"Opaque pagination cursor from a previous response's `next_cursor`.","schema":{"type":"string"}}],"responses":{"200":{"description":"Page of active markets.","headers":{"ETag":{"schema":{"type":"string"}},"Cache-Control":{"schema":{"type":"string","example":"public, max-age=30"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketListResponse"},"example":{"exchange_id":"kalshi","markets":[{"exchange_id":"kalshi","market_id":"KXBTC-26JUL","condition_id":"","event_id":"KXBTC-26JUL-EVT","title":"Bitcoin price above $120k on July 31?","neg_risk":false,"outcomes":[{"outcome":"Yes","normalized_outcome":"yes","token_id":"KXBTC-26JUL-YES","outcome_index":0,"side":"yes"},{"outcome":"No","normalized_outcome":"no","token_id":"KXBTC-26JUL-NO","outcome_index":1,"side":"no"}],"raw":{}}],"count":1,"next_cursor":"eyJvZmZzZXQiOjEwMH0=","has_more":true}}}},"304":{"description":"Not Modified — `If-None-Match` matched the current ETag."},"400":{"description":"Missing/unknown `provider`, or `limit` out of range.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"invalid_request","message":"limit must be an integer in [1, 250]"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/IPNotWhitelisted"},"404":{"description":"The upstream metadata cache answered the listing request with 404.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"not_found","message":"market not found"}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"`internal` / `authentication backend unavailable` — the API-key path could not reach the credential store.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"internal","message":"authentication backend unavailable"}}}}},"502":{"description":"The metadata cache answered with an unexpected status (`upstream` / `metadata cache request failed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"upstream","message":"metadata cache request failed"}}}}},"503":{"description":"`cache_cold` — the provider's active-market listing has not been populated yet (`Retry-After: 5`); `unavailable` — not configured for this deployment; or `rate_limiter_unavailable`.","headers":{"Retry-After":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"x-kairos-bucket":"light"}},"/v1/markets/tick-size":{"get":{"operationId":"getMarketTickSize","x-kairos-auth":"public","x-kairos-rate-limit":"1 light unit per request","summary":"Current valid tick grid for one market","tags":["Markets"],"description":"Returns the grid of valid price increments the venue CURRENTLY enforces for a market — the same grid the order executor validates against. It describes the grid; it never snaps or rounds a price. Served from the market metadata cache, which the orderbook streamer keeps current with the venue's live tick changes. Rate-limit bucket: LIGHT.\n\n- **polymarket** — a single flat range over `[0, 1]`; the venue flips 0.01↔0.001 at the price extremes. Pass `asset_id` (the CLOB token id) to read the per-token tick, which is where a live change lands first.\n- **kalshi** — the market's `price_ranges` (a tapered grid). `min_tick` is the finest step. If only the deprecated flat `tick_size` is present the response is a single flat range flagged `synthetic: true`.\n- **predictfun** — `supported: false`; the venue exposes no per-market tick. No grid is fabricated.\n\nQuery parameters and the response body are wire-compatible with the Data API's `GET /markets/tick-size`; `as_of` (when the grid was read from the metadata cache) is additive. `start` / `end` / `step` / `min_tick` are decimal strings so precision is never lost. Cached for 5 seconds (`public, max-age=5` with a strong ETag).","parameters":[{"name":"provider","in":"query","required":true,"schema":{"type":"string","enum":["kalshi","polymarket","predictfun"]},"example":"kalshi"},{"name":"contract_id","in":"query","required":true,"description":"Kalshi ticker, or Polymarket condition id / market id.","schema":{"type":"string"},"example":"KXBTC15M-26JUL221600-00"},{"name":"asset_id","in":"query","required":false,"description":"Polymarket CLOB token id. Enables the per-token read; ignored for other providers.","schema":{"type":"string"}}],"responses":{"200":{"description":"The market's current tick grid, or the explicit unsupported payload for predictfun.","headers":{"ETag":{"schema":{"type":"string"}},"Cache-Control":{"schema":{"type":"string","example":"public, max-age=5"}}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/TickGrid"},{"$ref":"#/components/schemas/TickGridUnsupported"}]},"examples":{"kalshi":{"value":{"provider":"kalshi","contract_id":"KXBTC15M-26JUL221600-00","asset_id":null,"ranges":[{"start":"0","end":"0.04","step":"0.001"},{"start":"0.04","end":"0.96","step":"0.01"},{"start":"0.96","end":"1","step":"0.001"}],"min_tick":"0.001","source":"kalshi_price_ranges","synthetic":false,"price_level_structure":"tapered","as_of":"2026-09-18T12:00:00Z"}},"polymarket":{"value":{"provider":"polymarket","contract_id":"0xabc123","asset_id":"18812649149814341758733697580460697418474693998558159483117","ranges":[{"start":"0","end":"1","step":"0.001"}],"min_tick":"0.001","source":"metadata_cache","synthetic":false,"price_level_structure":null,"as_of":"2026-09-18T12:00:00Z"}},"predictfun":{"value":{"provider":"predictfun","contract_id":"42","supported":false,"reason":"predict.fun exposes no per-market tick endpoint or tick change event; treat its tick as static/default. See docs/exchange-docs."}}}}}},"304":{"description":"Not Modified — `If-None-Match` matched the current ETag."},"400":{"description":"Unknown provider, a provider with no per-market grid (hyperliquid, opinion), or an empty `contract_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"invalid_request","message":"Provider 'hyperliquid' has no per-market tick grid. Supported: kalshi, polymarket (predictfun is static)."}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/IPNotWhitelisted"},"404":{"description":"The metadata cache has no such market (after its ClickHouse and live-venue read-through).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"not_found","message":"market not found"}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"`tick_unavailable` — the market exists but carries no usable grid (no Polymarket tick; a Kalshi market whose cached `raw` is the catalog row rather than the venue object, so it has no `price_ranges`; or a venue object with neither `price_ranges` nor `tick_size`); `upstream` — the metadata cache answered with an unexpected status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"tick_unavailable","message":"Kalshi market KXFOO has neither price_ranges nor tick_size"}}}}},"503":{"description":"`cache_cold` — the metadata cache is still warming (with `Retry-After: 5`); `unavailable` — no metadata cache is configured; or `rate_limiter_unavailable`.","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Present only for `cache_cold`."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"x-kairos-bucket":"light"}},"/v1/markets/tick-size/batch":{"post":{"operationId":"batchGetMarketTickSize","x-kairos-auth":"public","x-kairos-rate-limit":"1 light unit per request, regardless of batch size","summary":"Current tick grids for up to 200 markets of one provider","tags":["Markets"],"description":"The same resolution as `GET /v1/markets/tick-size` for up to 200 `(contract_id, asset_id)` pairs of ONE provider in one round trip. Polymarket token ids are read from the metadata cache in a single batch. Rate-limit bucket: LIGHT, flat cost of 1 unit. Not cached (`Cache-Control: no-store`).\n\n`results` is positional — `results[i]` answers `items[i]` — and each entry is either a tick grid, the predictfun unsupported payload, or a per-item error carrying the same `code` the single route would have answered with (`not_found`, `tick_unavailable`, `cache_cold`, `upstream`), or `timeout` / `cancelled` for items not started before the 15s batch bound or the caller left. A batch where every item failed is still a `200`; only request-level problems (bad body, unknown provider, an empty `contract_id`, more than 200 items, or a failed token prefetch) fail the whole call.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TickGridBatchRequest"},"example":{"provider":"polymarket","items":[{"contract_id":"0xabc123","asset_id":"18812649149814341758733697580460697418474693998558159483117"},{"contract_id":"0xdef456"}]}}}},"responses":{"200":{"description":"Positional results, one per requested item.","headers":{"Cache-Control":{"schema":{"type":"string","example":"no-store"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TickGridBatchResponse"},"example":{"provider":"polymarket","results":[{"provider":"polymarket","contract_id":"0xabc123","asset_id":"18812649149814341758733697580460697418474693998558159483117","ranges":[{"start":"0","end":"1","step":"0.001"}],"min_tick":"0.001","source":"metadata_cache","synthetic":false,"price_level_structure":null,"as_of":"2026-09-18T12:00:00Z"},{"contract_id":"0xdef456","asset_id":null,"error":{"code":"not_found","message":"market not found"}}]}}}},"400":{"description":"Unreadable/invalid JSON body, unknown provider or one with no per-market grid, missing or empty `items`, an item with an empty `contract_id`, or more than 200 items.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"invalid_request","message":"items exceeds maximum 200"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/IPNotWhitelisted"},"429":{"$ref":"#/components/responses/RateLimited"},"502":{"description":"`upstream` — the Polymarket token prefetch failed with an unexpected metadata-cache status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"`cache_cold` (with `Retry-After: 5`), `unavailable`, or `rate_limiter_unavailable`.","headers":{"Retry-After":{"schema":{"type":"string"},"description":"Present only for `cache_cold`."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"x-kairos-bucket":"light"}},"/v1/resolutions":{"get":{"operationId":"getResolutions","x-kairos-auth":"public","x-kairos-rate-limit":"1 light unit per request","summary":"Batch resolution fractions for up to 200 markets","tags":["Resolutions"],"description":"Returns the resolved YES-side fraction (payout_numerators[0] / sum) for each requested market that has resolved; unresolved markets are omitted entirely from the response (never a guessed value). Providers are keyed either directly by market id/ticker (kalshi) or by their on-chain condition id (polymarket, predictfun, opinion). Rate-limit bucket: LIGHT.\n\nFor scalar/range markets with no payout numerators, the venue's settled value is used as the fraction instead of a YES/NO split (kalshi-keyed providers only).\n\nThe response is ETagged (strong ETag) with a `Cache-Control` header; a matching `If-None-Match` returns 304.","parameters":[{"name":"provider","in":"query","required":true,"description":"Venue identifier. Any registered provider (including disabled ones, for historic reads).","schema":{"type":"string","enum":["kalshi","polymarket","opinion","predictfun","hyperliquid","kalshi_offchain","dome"]},"example":"kalshi"},{"name":"market_ids","in":"query","required":true,"description":"Comma-separated market ids/tickers. Up to 200. Empty entries are dropped.","schema":{"type":"string"},"example":"KXBTC-26JUL,KXETH-26JUL"}],"responses":{"200":{"description":"Map of market_id to resolved YES-side fraction, for resolved markets only.","headers":{"ETag":{"schema":{"type":"string"}},"Cache-Control":{"schema":{"type":"string","example":"public, max-age=3600"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolutionsResponse"},"example":{"resolutions":{"KXBTC-26JUL":1,"KXETH-26JUL":0.5}}}}},"304":{"description":"Not Modified — `If-None-Match` matched the current ETag."},"400":{"description":"Unknown provider, or `market_ids` missing/empty/exceeds 200.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"invalid_request","message":"market_ids exceeds maximum 200"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/IPNotWhitelisted"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The resolutions query failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"internal","message":"resolutions query failed"}}}}},"503":{"$ref":"#/components/responses/RateLimiterUnavailable"}},"x-kairos-bucket":"light"}},"/v1/markets/{provider}/{market_id}/resolution":{"get":{"operationId":"getMarketResolutionStatus","x-kairos-auth":"public","x-kairos-rate-limit":"1 light unit per request","summary":"Current resolution lifecycle snapshot for one market","tags":["Resolutions"],"description":"Returns the current resolution state for one market — status (e.g. proposed/disputed/resolved), the UMA-style proposal/dispute metadata, and (once resolved) the payout numerators. For kalshi-style providers the ticker identifies the market directly; for CTF venues (polymarket, predictfun, opinion) the market id is resolved to its on-chain condition id internally. Rate-limit bucket: LIGHT.\n\nThe response caches for a short window (`Cache-Control` header with a strong ETag / 304 support) because proposals move through a roughly 2-hour challenge window — a longer cache lifetime would routinely misreport \"proposed\" as already final.","parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string","enum":["kalshi","polymarket","opinion","predictfun","hyperliquid","kalshi_offchain","dome"]},"example":"polymarket"},{"name":"market_id","in":"path","required":true,"description":"For kalshi, the ticker (used directly as market_key). For CTF venues, the market id — resolved to its condition_id internally.","schema":{"type":"string"},"example":"516710"}],"responses":{"200":{"description":"Resolution lifecycle snapshot.","headers":{"ETag":{"schema":{"type":"string"}},"Cache-Control":{"schema":{"type":"string","example":"public, max-age=30"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolutionStatus"},"example":{"provider":"polymarket","market_id":"516710","condition_id":"0xcond123","status":"proposed","proposed_price":0.5,"proposed_at":"2026-07-15T09:30:00+00:00","challenge_window_ends_at":null,"proposer":"0xprop","disputer":"","dispute_count":1,"reset_count":0,"payout_numerators":[],"resolved_ts":null,"last_event_ts":"2026-07-15T09:30:00+00:00"}}}},"304":{"description":"Not Modified — `If-None-Match` matched the current ETag."},"400":{"description":"Unknown provider.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/IPNotWhitelisted"},"404":{"description":"No resolution state row for this market (or, for CTF venues, no on-chain condition id mapping was found).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"not_found","message":"no resolution state for market"}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The resolution status query failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"internal","message":"resolution status query failed"}}}}},"503":{"$ref":"#/components/responses/RateLimiterUnavailable"}},"x-kairos-bucket":"light"}},"/v1/markets/{provider}/{market_id}/resolution/events":{"get":{"operationId":"getMarketResolutionEvents","x-kairos-auth":"public","x-kairos-rate-limit":"1 light unit per request","summary":"Full resolution lifecycle timeline for one market","tags":["Resolutions"],"description":"Returns the append-only event timeline (proposed, disputed, reset, settled, etc.) for a market's resolution, oldest first, capped at 200 events, keyed the same way as `.../resolution` (kalshi ticker identifies the market directly; CTF venues resolve market_id to its on-chain condition id first). Duplicate ingestion events are de-duplicated before ordering chronologically. Rate-limit bucket: LIGHT.\n\nCache lifetime matches the resolution-status endpoint: a `Cache-Control` header with a strong ETag (304 on matching `If-None-Match`).","parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string","enum":["kalshi","polymarket","opinion","predictfun","hyperliquid","kalshi_offchain","dome"]},"example":"kalshi"},{"name":"market_id","in":"path","required":true,"description":"For kalshi, the ticker (used directly as market_key). For CTF venues, the market id — resolved to its condition_id internally.","schema":{"type":"string"},"example":"TICK-A"}],"responses":{"200":{"description":"Resolution event timeline (possibly empty if the market has resolution state but no recorded events yet).","headers":{"ETag":{"schema":{"type":"string"}},"Cache-Control":{"schema":{"type":"string","example":"public, max-age=30"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolutionEventsResponse"},"example":{"provider":"kalshi","market_id":"TICK-A","market_key":"TICK-A","events":[{"event_type":"proposed","source":"polygon","event_ts":"2026-07-01T10:00:00+00:00","price_norm":1,"too_early":true,"proposer":"0xprop","bond":"500000000000","reward":"5000000","expiration_ts":"2026-07-01T12:00:00+00:00","request_timestamp":"2026-07-01T09:00:00+00:00","tx_hash":"0xdead","block_number":123},{"event_type":"settled_offchain","source":"kalshi_api","event_ts":"2026-07-01T13:00:00+00:00","payout_numerators":[1,0]}]}}}},"304":{"description":"Not Modified — `If-None-Match` matched the current ETag."},"400":{"description":"Unknown provider.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/IPNotWhitelisted"},"404":{"description":"No resolution state for this market — for CTF venues this means no condition_id mapping was found (events cannot be looked up without one).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"not_found","message":"no resolution state for market"}}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The resolution events query failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"internal","message":"resolution events query failed"}}}}},"503":{"$ref":"#/components/responses/RateLimiterUnavailable"}},"x-kairos-bucket":"light"}},"/v1/marks":{"get":{"operationId":"getMarks","x-kairos-auth":"public","x-kairos-rate-limit":"1 heavy unit per started 100 pairs (2 units at the 200-pair cap)","summary":"Latest mark (last trade price) for up to 200 contract/token pairs","tags":["Marks"],"description":"A mark is defined as the LAST TRADE PRICE, on the 0–100 scale used by candles/trades (not the 0–1 probability scale used by PnL consumers). Rate-limit bucket: HEAVY and explicitly uncacheable at the HTTP layer (`Cache-Control: no-store`, no ETag); real-time consumers should prefer the WebSocket feed instead.\n\nPairs that have never traded are omitted from the response entirely (no zero/null placeholder).\n\nCost: larger requests consume more of your quota based on the number of pairs requested. Since the hard cap is 200 pairs, the maximum possible cost for one request is 2 heavy units.","parameters":[{"name":"provider","in":"query","required":true,"schema":{"type":"string","enum":["kalshi","polymarket","opinion","predictfun","hyperliquid","kalshi_offchain","dome"]},"example":"polymarket"},{"name":"pairs","in":"query","required":true,"description":"Comma-separated `contract_id:token_id` pairs. Up to 200. Each pair must contain a non-empty contract_id and token_id separated by exactly one colon.","schema":{"type":"string"},"example":"0xabc123:18812649149814341758733697580460697418474693998558159483117,0xabc123:88123409981238091823740918237409182734091823740918237409182"}],"responses":{"200":{"description":"Marks for the requested pairs, in request order; never-traded pairs are omitted.","headers":{"Cache-Control":{"schema":{"type":"string","example":"no-store"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarksResponse"},"example":{"marks":[{"contract_id":"0xabc123","token_id":"18812649149814341758733697580460697418474693998558159483117","price":63.5}]}}}},"400":{"description":"Unknown provider, missing `pairs`, `pairs` exceeds 200, or a pair is not in `contract_id:token_id` form.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"invalid_request","message":"pairs exceeds maximum 200"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/IPNotWhitelisted"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The marks lookup failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"internal","message":"marks query failed"}}}}},"503":{"$ref":"#/components/responses/RateLimiterUnavailable"}},"x-kairos-bucket":"heavy"}},"/v1/candles":{"get":{"operationId":"getCandles","x-kairos-auth":"public","x-kairos-rate-limit":"1 light unit + 1 per 5000 requested bars (window ÷ timeframe, after retention clamping)","summary":"Get a single OHLCV candle series","tags":["Candles"],"x-kairos-bucket":"light","description":"Returns OHLCV candles for one `(provider, contract_id, timeframe_seconds, outcome)`\nseries over `[start, end)`.\n\n**Alignment.** `start` is floored and `end` is ceiled to the nearest\n`timeframe_seconds` bucket boundary; `end` is additionally clamped to\n`now + 1 bucket`. Degenerate windows (`end <= start` after alignment)\nare extended by exactly one bucket. The requested window is silently\nclamped (not rejected) to the per-timeframe retention ceiling: 1s=1\nday, 1m=30 days, 5m=90 days, 15m=180 days, 1h=365 days, 4h=365 days,\n1d=730 days. For `timeframe_seconds=1`, if the aligned window falls\nentirely before `now - 24h` (the retention window for 1-second\ncandles) the endpoint short-circuits to `{\"candles\":[]}` without\nquerying upstream.\n\n**Timeframes.** Only 1s and 1m candles are stored directly; every\nother timeframe (5m, 15m, 1h, 4h, 1d) is rolled up server-side from\nthe 1m base. When the first read comes back empty, `60`, `3600`,\n`14400` and `86400` retry as a rollup of the 1s base (brand-new\ncontracts whose only history is 1-second candles); `300` and `900`\nhave no such fallback and return an empty series instead. Prices are\non a 0-100 scale. For providers that are not natively per-token\n(i.e. not `kalshi`, `polymarket`, `dome`, `opinion`, `predictfun`,\n`hyperliquid`), requesting `outcome > 0` inverts prices\n(`price = 100 - price`, high/low swapped) rather than resolving a\ndistinct token. The check is on the provider string exactly as\nsubmitted, so the `kalshi_offchain` alias takes the inverting path\neven though `kalshi` does not — send `kalshi` for Kalshi candles.\n\n**Rate limiting / cost.** This route is in the `light` rate-limit\nbucket. Larger requests — wider windows, finer timeframes — consume\nmore of your quota; a typical chart paint (a few hundred bars) costs\n1 unit.\n\n**Caching.** `Cache-Control` depends on how the aligned window\nrelates to `now`: a window still in progress (`end >= now`) is\n`no-store`; a window entirely in the recent past is cached briefly;\nanything older is cached for longer. Cacheable responses carry a\nstrong ETag and honor `If-None-Match`, returning `304` on a match.\nResponses ≥1KB are gzip-encoded when the client sends\n`Accept-Encoding: gzip`.\n\n**Binary format.** Pass `?fmt=binary` or send\n`Accept: application/x-kairos-candles` to receive a compact\ncolumnar binary frame instead of JSON. Layout: `u8 magic=0xCA,\nu8 version=1, u16 num_results`, then per result `u32 index,\nu32 count`, followed by the columnar arrays `u32[count] t` (epoch\nseconds), `u16[count] o,h,l,c` (price ×100), `i64[count] vol`\n(×100). A single-series `GET` response always has `num_results=1`.\n","parameters":[{"name":"provider","in":"query","required":true,"schema":{"type":"string","enum":["kalshi","polymarket","opinion","predictfun","hyperliquid","dome","kalshi_offchain"]},"description":"Market-data provider. Case-insensitive. `dome` and `kalshi_offchain` are aliases resolved to `polymarket` and `kalshi` respectively. `opinion` is a disabled provider kept resolvable for historic reads only. Unknown values return `400 invalid_request`.\n","example":"polymarket"},{"name":"contract_id","in":"query","required":true,"schema":{"type":"string","maxLength":128},"description":"Provider-native contract/market/ticker id. For polymarket this may be either a numeric market id or a `0x`-prefixed condition_id (both resolve to the same series). Empty or >128 chars is rejected with `400 invalid_request`.\n","example":"21742633143463906290569050155826241533067272736897614950488156847949938836455"},{"name":"timeframe_seconds","in":"query","required":true,"schema":{"type":"integer","enum":[1,60,300,900,3600,14400,86400]},"description":"Candle bucket width in seconds. Any value outside this set is rejected with `400 invalid_request`. Only 1 and 60 are stored directly; 300/900/3600/14400/86400 are always served as rollups.\n","example":3600},{"name":"start","in":"query","required":true,"schema":{"type":"string"},"description":"Window start, inclusive (before alignment). Accepts RFC 3339 (`2024-01-15T10:00:00Z` or with a numeric offset), a bare datetime (`2024-01-15T10:00:00`, treated as UTC), or a bare date (`2024-01-15`, treated as UTC midnight). Unparseable values return `400 invalid_request`.\n","example":"2026-07-15T00:00:00Z"},{"name":"end","in":"query","required":true,"schema":{"type":"string"},"description":"Window end, exclusive (before alignment). Same accepted formats as `start`. Must be strictly after `start` or the request is rejected with `400 invalid_request: end must be after start`.\n","example":"2026-07-16T00:00:00Z"},{"name":"outcome","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0},"description":"Zero-based outcome index (e.g. 0=Yes, 1=No for a binary market, or an index into a multi-outcome market's token list). Defaults to 0. Negative or non-integer values return `400 invalid_request`.\n","example":0},{"name":"fmt","in":"query","required":false,"schema":{"type":"string","enum":["binary"]},"description":"Set to `binary` to receive the columnar binary frame instead of JSON. Equivalent to sending `Accept: application/x-kairos-candles`."}],"responses":{"200":{"description":"Candle series for the requested window. `{\"candles\":[]}` (empty array, `no-store`) is a valid 200 response, not an error — it means the window is authoritatively empty.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CandleListResponse"},"examples":{"hourlyCandles":{"value":{"candles":[{"contract_id":"21742633143463906290569050155826241533067272736897614950488156847949938836455","timeframe_seconds":3600,"bucket_start":"2026-07-15T00:00:00+00:00","open":61.5,"high":63,"low":60.8,"close":62.4,"volume":184230,"token_id":"704721957297303853272349184"}]}}}},"application/x-kairos-candles":{"schema":{"$ref":"#/components/schemas/CandleBinaryFrame"}}}},"304":{"description":"Not Modified — `If-None-Match` matched the current ETag. Empty body."},"400":{"description":"Malformed or invalid query parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"unknownProvider":{"value":{"error":{"code":"invalid_request","message":"unknown provider \"foo\""}}},"badTimeframe":{"value":{"error":{"code":"invalid_request","message":"invalid timeframe_seconds (valid: 1, 60, 300, 900, 3600, 14400, 86400)"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/IPNotWhitelisted"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Upstream candle fetch failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/RateLimiterUnavailable"}}}},"/v1/candles/batch":{"post":{"operationId":"postCandlesBatch","x-kairos-auth":"public","x-kairos-rate-limit":"1 light unit admitted up front + a deferred charge of 1 per 5000 requested bars summed over every item","summary":"Get up to 200 candle series in one request","tags":["Candles"],"x-kairos-bucket":"light","description":"Batched form of `GET /v1/candles`: fetches up to 200 independent\n`(provider, contract_id, timeframe_seconds, start, end, outcome)`\nseries concurrently and returns them in request order, with\n**per-index partial failure** — one item's validation or fetch\nerror does not fail the others.\n\nEach item is validated and aligned exactly as in `GET /v1/candles`\n(same clamping, rollup, and inversion rules). Body accepts either a\n`requests` array or a legacy `items` alias; if `requests` is\nnon-empty it is used, otherwise `items` is used.\n`timeframe_seconds` and `outcome` in each item may be a JSON number\nor a numeric string. The request body is capped at 4 MiB.\n\n**Rate limiting / cost.** Admitted at the base cost of 1\n`light`-bucket unit. Larger batches — more items, wider windows,\nfiner timeframes — consume more of your quota; any excess is\ncharged as a deferred extra charge against your next request.\n\n**Caching.** Batch responses are always `Cache-Control: no-store`\nand never carry an ETag.\n\n**Binary format.** Pass `?fmt=binary` or\n`Accept: application/x-kairos-candles` to receive a single\nmulti-result binary frame (`num_results = len(requests)`) — one\ncolumnar result section per request index, in input order. An item\nthat failed sets the high bit (`0x80000000`) of its section's\n`count`, so it stays distinguishable from a genuinely empty window;\nmask the bit off to read the (always zero) count. Per-item error\nmessages are only available in the JSON response.\n\n**Failure semantics.** Returns `500` only when *every* item in the\nbatch failed. Otherwise `200` with a mix of populated and\nempty/errored entries.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CandleBatchRequest"},"example":{"requests":[{"provider":"polymarket","contract_id":"21742633143463906290569050155826241533067272736897614950488156847949938836455","timeframe_seconds":3600,"start":"2026-07-15T00:00:00Z","end":"2026-07-16T00:00:00Z","outcome":0},{"provider":"kalshi","contract_id":"KXHIGHNY-26JUL15-T50","timeframe_seconds":60,"start":"2026-07-15T12:00:00Z","end":"2026-07-15T13:00:00Z"}]}}}},"responses":{"200":{"description":"Per-index results (populated, empty, or errored). Order matches the request array.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CandleBatchResponse"},"examples":{"mixedResult":{"value":{"results":[{"index":0,"candles":[{"contract_id":"21742633143463906290569050155826241533067272736897614950488156847949938836455","timeframe_seconds":3600,"bucket_start":"2026-07-15T00:00:00+00:00","open":61.5,"high":63,"low":60.8,"close":62.4,"volume":184230}]},{"index":1,"candles":[],"error":"unknown provider \"acme\""}]}}}},"application/x-kairos-candles":{"schema":{"$ref":"#/components/schemas/CandleBinaryFrame"}}}},"400":{"description":"Malformed body, empty/oversized `requests` array.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"tooLarge":{"value":{"error":{"code":"invalid_request","message":"batch size 250 exceeds maximum 200"}}},"empty":{"value":{"error":{"code":"invalid_request","message":"requests array is required"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/IPNotWhitelisted"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Every item in the batch failed (validation or fetch).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"default":{"value":{"error":{"code":"internal","message":"all batch items failed"}}}}}}},"503":{"$ref":"#/components/responses/RateLimiterUnavailable"}}}},"/v1/trades":{"get":{"operationId":"getTradeHistory","x-kairos-auth":"public","x-kairos-rate-limit":"1 heavy unit per started 250 rows of `limit` (2 units at limit=500)","summary":"Recent trade tape for a contract","description":"Returns the most recent individual trades for a single (provider, contract_id) pair,\nnewest first, deduplicated by trade id. Only rows with `price > 0 AND price <= 100 AND size > 0` are eligible.\n\n**Price unit**: `price` is on the integer-cents scale **0–100 for every provider**\n(Kalshi, Polymarket, predict.fun, Hyperliquid alike) — it is NOT a 0–1 probability and\nNOT already divided into dollars. Multiply `size * price / 100` to get USD notional.\n`price` is returned as a float because some venues (e.g. Kalshi) report sub-penny ticks.\n\n**Window semantics**: the query window is `[now - window_seconds, before_or_now]`. The\nlower bound is always computed from the *current* server time, not from `before` —\npassing a `before` older than `now - window_seconds` can invert the window. If the\nprimary window yields zero rows, the handler transparently retries with an unbounded\nlower bound `[0, before_or_now]` so contracts with no recent activity still return\ntheir most recent historical trades. `has_more` reflects an internal over-fetch of\n`limit + 1` rows, truncated back to `limit` before serialization.\n\n**Rate-limit cost (HEAVY bucket)**: priced by requested depth — a larger `limit`\nconsumes more of your quota.\n\n**Caching**: responses are cached for 3s per replica (with singleflight coalescing) to\nabsorb duplicate concurrent requests, and the implicit `now` upper bound is quantized to\nthat same 3s so the cache is usable at all. The HTTP response itself is\n`Cache-Control: private, max-age=5` with no ETag — this route never returns 304.\nResponses ≥1KB are gzip-compressed when the client sends `Accept-Encoding: gzip`.\n","tags":["Trades"],"parameters":[{"name":"provider","in":"query","required":true,"description":"Venue identifier, case-insensitive. Resolved against the central provider registry; `kalshi_offchain` is an alias for `kalshi` and `dome` is an alias for `polymarket`. `opinion` resolves but is a disabled provider — valid only for historic reads.\n","schema":{"type":"string","enum":["kalshi","kalshi_offchain","polymarket","dome","opinion","predictfun","hyperliquid"]},"example":"polymarket"},{"name":"contract_id","in":"query","required":true,"description":"Venue-scoped contract/token identifier to fetch trades for.","schema":{"type":"string"},"example":"0x8b1c119419f622e21fc576ec8e9c2c07b2c1b09babf0e1f3d1cb9bfc1a8f9c2"},{"name":"window_seconds","in":"query","required":false,"description":"Lookback window, in seconds, measured back from the current server time (not from `before`). Valid range [3600, 86400]; out-of-range or non-integer values are rejected with 400, not clamped.\n","schema":{"type":"integer","minimum":3600,"maximum":86400,"default":86400},"example":86400},{"name":"limit","in":"query","required":false,"description":"Maximum number of trades to return, newest first. Valid range is [1, 500]; the default is also 500. Drives the rate-limit cost — see the operation description.\n","schema":{"type":"integer","minimum":1,"maximum":500,"default":500},"example":100},{"name":"before","in":"query","required":false,"description":"Upper bound of the window as a positive Unix timestamp in seconds (exclusive: trades with `trade_ts < before`). Omit to use the current time. Must be a positive integer or the request is rejected with 400.\n","schema":{"type":"integer","format":"int64","minimum":1},"example":1737480000}],"responses":{"200":{"description":"Trade page for the window, newest first. `trades` may be empty (and `oldest_available_ts` null) if the contract has no recorded trades at all, even after the unbounded fallback query.\n","headers":{"X-RateLimit-Bucket":{"schema":{"type":"string","example":"heavy"}},"X-RateLimit-Tier":{"schema":{"type":"string","example":"api-key"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}},"X-RateLimit-Reset":{"schema":{"type":"integer","description":"Unix timestamp when the rate-limit window resets."}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TradeHistoryResponse"},"example":{"trades":[{"trade_id":"0x9f2a...c11","contract_id":"0x8b1c119419f622e21fc576ec8e9c2c07b2c1b09babf0e1f3d1cb9bfc1a8f9c2","size":250,"price":62.5,"outcome":"Yes","timestamp":1737479998.412,"token_id":"10945...3321","taker_address":"0xabc1234567890abcdef1234567890abcdef1234","side":"buy"}],"has_more":true,"oldest_available_ts":1737479950.001,"coverage_hours":0.01}}}},"400":{"description":"Validation failure: missing `provider`, unrecognized `provider`, missing `contract_id`, `window_seconds`/`limit` outside their bounds, or a non-positive / non-integer `before`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"invalid_request","message":"limit must be an integer in [1, 500]"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/IPNotWhitelisted"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The trade-history query failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/RateLimiterUnavailable"}},"x-kairos-bucket":"heavy"}},"/v1/trades/metrics":{"get":{"operationId":"getTradeMetrics","x-kairos-auth":"public","x-kairos-rate-limit":"1 light unit per request","summary":"Aggregate trade volume/pressure metrics for a contract window","description":"Returns aggregated volume and buy-pressure metrics for a (provider, contract_id) over\na trailing window, computed from the same deduplicated trade data as `/v1/trades`.\n\n**Two query shapes depending on provider**, because the platform cannot generically\ninfer which side of a market is \"Yes\":\n- **Kalshi**: `outcome_0` is the side where `outcome` or `token_id`\n  (case-insensitive) equals `\"yes\"`, `outcome_1` is `\"no\"`.\n- **All other providers**: volumes are grouped by `token_id` and the **top 2 tokens by\n  volume** are reported as `outcome_0`/`outcome_1` — a volume ranking, not a\n  guaranteed Yes/No mapping.\n\nUSD volume is always `size * price / 100` (price is cents 0–100 for every provider).\n`outcome_0_volume_share_pct` defaults to 50.0 when both outcome volumes are zero.\n`coverage_pct` measures how much of the requested window is actually backed by data\nand is 0 when no trades are found in the window.\n\n**Rate-limit cost (LIGHT bucket)**: flat 1 unit per request.\n\n**Caching**: responses are cached for 10s per replica (with singleflight coalescing) to\nabsorb duplicate concurrent requests. `Cache-Control: private, max-age=5`, no ETag —\nthis route never returns 304.\n","tags":["Trades"],"parameters":[{"name":"provider","in":"query","required":true,"description":"Venue identifier, case-insensitive. `kalshi_offchain` aliases `kalshi`; `dome` aliases `polymarket`. Whether `provider` resolves to Kalshi determines which aggregation query runs — see the operation description.\n","schema":{"type":"string","enum":["kalshi","kalshi_offchain","polymarket","dome","opinion","predictfun","hyperliquid"]},"example":"polymarket"},{"name":"contract_id","in":"query","required":true,"description":"Venue-scoped contract/token identifier to compute metrics for.","schema":{"type":"string"},"example":"0x8b1c119419f622e21fc576ec8e9c2c07b2c1b09babf0e1f3d1cb9bfc1a8f9c2"},{"name":"window_seconds","in":"query","required":false,"description":"Trailing window, in seconds, measured back from the current server time. Valid range [3600, 86400]; out-of-range or non-integer values are rejected with 400.\n","schema":{"type":"integer","minimum":3600,"maximum":86400,"default":86400},"example":3600}],"responses":{"200":{"description":"Aggregated metrics for the window. Present with all-zero volumes (and `coverage_pct: 0`) rather than a 404 when no trades exist.\n","headers":{"X-RateLimit-Bucket":{"schema":{"type":"string","example":"light"}},"X-RateLimit-Tier":{"schema":{"type":"string","example":"api-key"}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}},"X-RateLimit-Reset":{"schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TradeMetricsResponse"},"example":{"metrics":{"volume_usd":184032.55,"outcome_0_volume_usd":121004.1,"outcome_1_volume_usd":63028.45,"outcome_0_volume_share_pct":65.8,"trade_count":5123,"window_seconds":3600,"coverage_pct":97.2}}}}},"400":{"description":"Validation failure: missing `provider`, unrecognized `provider`, missing `contract_id`, or `window_seconds` outside [3600, 86400].\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"invalid_request","message":"window_seconds must be an integer in [3600, 86400]"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/IPNotWhitelisted"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"The trade-metrics query failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/RateLimiterUnavailable"}},"x-kairos-bucket":"light"}},"/v1/perpetuals/{venue}/{instrument}/snapshot":{"get":{"operationId":"getPerpetualSnapshot","x-kairos-auth":"public","x-kairos-rate-limit":"6 heavy units per request (the snapshot fans out to several venue resources)","summary":"Get a live perpetual market-data snapshot","tags":["Perpetuals"],"description":"Production beta, sourced directly from the selected venue by the Market Data API.\nReturns a canonical ordered book, recent trades, one-minute candles,\nfunding observations, and typed market state. Financial values are\nexact decimal strings. Prices are direct venue prices, never 0–100\nprediction probabilities. Quantities and volumes retain the declared\n`base_asset` or `contracts` unit. Do not derive notional for contract\nquantities without authoritative instrument metadata, and never apply\nthe prediction-market `/ 100` formula. Each request costs 6 units in\nthe HEAVY rate-limit bucket because it fans out to multiple venue\nresources.\nResponses are never cacheable by clients; the Market Data API uses a bounded\none-second replica-local cache to coalesce bursts.\n","parameters":[{"name":"venue","in":"path","required":true,"schema":{"type":"string","enum":["hyperliquid","polymarket_perps","kalshi_margin"]}},{"name":"instrument","in":"path","required":true,"description":"Venue-native instrument identifier, such as BTC, 6, or KXBTCPERP.\nHyperliquid support is limited to standard main-dex perps in every\nenvironment; HIP-3 `dex:coin` identifiers are rejected. BTC and other\nstandard listings are quoted in USDT, while HYPE and PURR are\nUSDC-quoted exceptions. All three collateralize and settle in USDC.\nTheir canonical identities are `hl-mainnet-btc-usdt`,\n`hl-mainnet-hype-usdc`, and `hl-mainnet-purr-usdc`.\n","schema":{"type":"string"}},{"name":"depth","in":"query","description":"Requested book depth per side. When omitted the default is the venue's own ceiling —\n20 for `hyperliquid` (its `l2Book` returns no more), 500 for `polymarket_perps` and\n`kalshi_margin`. A value outside [1, 500], or one that is not an integer, is rejected\nwith 400.\n","schema":{"type":"integer","minimum":1,"maximum":500}}],"responses":{"200":{"description":"Live canonical venue snapshot.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PerpetualSnapshot"}}}},"400":{"description":"`unsupported perpetual venue` (a `venue` outside the enum), `depth must be an integer`, `depth must be between 1 and 500`, or `invalid perpetual instrument` — the instrument is empty, longer than 128 bytes, or does not match the venue's identifier grammar (`^[A-Za-z0-9][A-Za-z0-9._-]*$` for Hyperliquid, which is what rejects HIP-3 `dex:coin`; a positive integer for `polymarket_perps`; `^KX[A-Z0-9]+PERP$` for `kalshi_margin`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"invalid_request","message":"invalid perpetual instrument"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/IPNotWhitelisted"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"`internal` / `authentication backend unavailable` — the API-key path could not reach the credential store.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"internal","message":"authentication backend unavailable"}}}}},"502":{"description":"`upstream` / `perpetual venue data is unavailable or invalid`. Covers every non-validation failure: the venue returned a non-2xx status or an undecodable body, the 12s composite fetch deadline expired, or the assembled snapshot failed canonical validation (crossed or unsorted book, non-exact decimal, unknown market status, bad candle interval, unknown funding sign convention).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/RateLimiterUnavailable"}},"x-kairos-bucket":"heavy"}}},"components":{"securitySchemes":{"apiKeyClientId":{"type":"apiKey","in":"header","name":"X-Client-Id","description":"Credential client id (`kairos_ck_...`). Must be sent together with X-Api-Key and X-Api-Secret."},"apiKeyKey":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"64-char hex API key. Must be sent together with X-Client-Id and X-Api-Secret."},"apiKeySecret":{"type":"apiKey","in":"header","name":"X-Api-Secret","description":"64-char hex API secret. Must be sent together with X-Client-Id and X-Api-Key."}},"responses":{"Unauthorized":{"description":"No valid credential presented. Every path returns `code: unauthorized`; the triggers differ:\n\n- An `Authorization` header on a deployment where bearer auth is not enabled (`bearer authentication is not enabled`).\n- A bearer token that fails verification (`invalid or expired session`), or whose token/session has been revoked (`session has been revoked`).\n- An incomplete or wrong API-key header set. Sending *any* of `X-Client-Id`, `X-Api-Key`, `X-Api-Secret` commits the request to the API-key path, so a partial set is rejected rather than silently degrading to anonymous limits (`valid X-Client-Id, X-Api-Key, and X-Api-Secret headers are required`).\n- No credential material at all while the anonymous free tier is unavailable — switched off for the deployment, forced off through the runtime kill switch, or not yet resolvable on a replica that has never read the flag (it fails closed).\n","headers":{"WWW-Authenticate":{"schema":{"type":"string"},"description":"`Bearer realm=\"market-data-api\"` on bearer-verification failure, `APIKey realm=\"market-data-api\", header=\"X-Client-Id, X-Api-Key, X-Api-Secret\"` on missing/invalid API-key credentials. Absent on the revoked-session and bearer-disabled paths."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"unauthorized","message":"valid X-Client-Id, X-Api-Key, and X-Api-Secret headers are required"}}}}},"IPNotWhitelisted":{"description":"API key presented from a source IP not in that credential's whitelist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"ip_not_whitelisted","message":"source IP not in credential whitelist"}}}}},"RateLimited":{"description":"Two different gates return this status, both with `code: rate_limited`:\n\n- **Per-credential (or per-IP, when anonymous) weighted budget exhausted.** The message is `rate limit exceeded`, or, for anonymous callers, `anonymous rate limit exceeded — request an API key for higher limits (…)`. `Retry-After` is the whole number of seconds until the current one-minute window rolls over, floored at 1. All four `X-RateLimit-*` headers are present.\n- **Per-IP admission gate.** An in-process, pre-authentication guard that keeps a flood off the shared credential cache and database. The message is `too many requests from this address` and `Retry-After` is a flat `60`. Because this fires *before* authentication, the response carries **no** `X-RateLimit-*` headers at all.\n","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the caller may retry — window-rollover seconds for the weighted limiter, a flat 60 for the IP admission gate."},"X-RateLimit-Bucket":{"schema":{"type":"string","enum":["light","heavy"]}},"X-RateLimit-Tier":{"schema":{"type":"string","enum":["anonymous","api-key","user"]}},"X-RateLimit-Remaining":{"schema":{"type":"integer"}},"X-RateLimit-Reset":{"schema":{"type":"integer","description":"Unix timestamp (seconds) when the current window resets."}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"rate_limited","message":"rate limit exceeded"}}}}},"RateLimiterUnavailable":{"description":"The rate limiter is unreachable. Fails CLOSED — the request is rejected rather than let through unmetered.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":{"code":"rate_limiter_unavailable","message":"rate limiter temporarily unavailable"}}}}}},"schemas":{"PerpetualExactDecimalString":{"type":"string","pattern":"^(?:0|-?[1-9][0-9]*(?:\\.[0-9]*[1-9])?|-?0\\.[0-9]*[1-9])$","description":"Normalized, exact base-10 decimal. No exponent notation, leading zeros,\ntrailing fractional zeros, NaN, or infinity. May be negative.\n","examples":["0","-0.00025","65192.5"]},"PerpetualPositiveDecimalString":{"type":"string","pattern":"^(?:[1-9][0-9]*(?:\\.[0-9]*[1-9])?|0\\.[0-9]*[1-9])$","description":"Normalized, exact base-10 decimal strictly greater than zero.","examples":["0.001","65192.5"]},"PerpetualNonNegativeDecimalString":{"type":"string","pattern":"^(?:0|[1-9][0-9]*(?:\\.[0-9]*[1-9])?|0\\.[0-9]*[1-9])$","description":"Normalized, exact base-10 decimal greater than or equal to zero.","examples":["0","1250.75"]},"PerpetualBookLevel":{"type":"object","additionalProperties":false,"required":["price","quantity","order_count"],"properties":{"price":{"$ref":"#/components/schemas/PerpetualPositiveDecimalString","description":"Direct venue price. This is not a 0–100 probability."},"quantity":{"$ref":"#/components/schemas/PerpetualPositiveDecimalString","description":"Quantity in the enclosing book's `native_volume_unit`."},"order_count":{"type":["integer","null"],"description":"Venue-reported order count at this level, or null when unavailable."}}},"PerpetualOrderBookSnapshot":{"type":"object","additionalProperties":false,"required":["bids","asks","event_time_ns","received_time_ns","source_sequence","sequence_domain","feed_mode","depth","requested_depth","source_depth_limit","depth_limited","native_volume_unit"],"properties":{"bids":{"type":"array","description":"Price levels ordered strictly highest to lowest.","items":{"$ref":"#/components/schemas/PerpetualBookLevel"}},"asks":{"type":"array","description":"Price levels ordered strictly lowest to highest.","items":{"$ref":"#/components/schemas/PerpetualBookLevel"}},"event_time_ns":{"type":["integer","null"],"format":"int64","description":"Venue event time in Unix nanoseconds, or null when unavailable."},"received_time_ns":{"type":"integer","format":"int64","description":"Kairos receive time in Unix nanoseconds."},"source_sequence":{"type":["integer","null"],"format":"int64","description":"Venue-native source sequence, or null for an unsequenced source."},"sequence_domain":{"type":"string","minLength":1,"description":"Scope in which `source_sequence` has meaning."},"feed_mode":{"type":"string","enum":["snapshot_only"],"description":"The preview exposes complete REST snapshots, not deltas."},"depth":{"type":"integer","minimum":0,"maximum":500,"description":"Number of levels returned on the deeper populated side."},"requested_depth":{"type":"integer","minimum":1,"maximum":500,"description":"Depth requested by the caller."},"source_depth_limit":{"type":["integer","null"],"minimum":1,"description":"Authoritative maximum depth imposed by the source endpoint, or null\nwhen the source does not declare a fixed limit.\n"},"depth_limited":{"type":"boolean","description":"True when the returned depth is below `requested_depth` because of\nan identified source limit. False does not imply both sides contain\nthe requested number of populated levels.\n"},"native_volume_unit":{"type":"string","enum":["base_asset","contracts"],"description":"Native quantity unit for every level in this book."}}},"PerpetualInstrumentContext":{"type":"object","additionalProperties":false,"required":["base_asset_id","quote_asset_id","collateral_asset_id","settlement_asset_id","price_unit","quantity_unit","contract_multiplier","notional_formula"],"properties":{"base_asset_id":{"type":"string","minLength":1},"quote_asset_id":{"type":"string","minLength":1,"description":"Asset in which direct prices are expressed. Hyperliquid standard\nmain-dex perpetuals use USDT.\n"},"collateral_asset_id":{"type":"string","minLength":1,"description":"Asset securing margin. This is independent of the quote asset;\nHyperliquid standard main-dex perpetuals use USDC collateral.\n"},"settlement_asset_id":{"type":"string","minLength":1,"description":"Asset in which settlement or realized PnL is denominated.\nHyperliquid uses USDC, Polymarket Perps uses pUSD, and Kalshi\nMargin uses USD.\n"},"price_unit":{"type":"string","minLength":1,"description":"Explicit unit relationship for every direct price in the snapshot.\nA perpetual price is never a 0–100 prediction probability.\n"},"quantity_unit":{"type":"string","enum":["base_asset","contracts"],"description":"Native quantity unit used by books and trades. Candle volume declares\nits own `native_volume_unit` and may differ; for example, a venue can\nquote book/trade quantities as contracts but kline volume as base asset.\n"},"contract_multiplier":{"anyOf":[{"$ref":"#/components/schemas/PerpetualPositiveDecimalString"},{"type":"null"}],"description":"Authoritative venue contract multiplier, or null when unavailable.\nConsumers must not invent a multiplier.\n"},"notional_formula":{"type":"string","minLength":1,"description":"Machine-readable formula identity describing any supported notional\nconversion. It is not the prediction-market `size * price / 100`\nformula.\n"}}},"PerpetualPriceObservation":{"type":"object","additionalProperties":false,"required":["price_type","value","observed_time_ns"],"properties":{"price_type":{"type":"string","minLength":1,"description":"Venue-backed price identity, such as mark, index, oracle, or mid."},"value":{"$ref":"#/components/schemas/PerpetualPositiveDecimalString","description":"Direct venue price; never a prediction probability."},"observed_time_ns":{"type":["integer","null"],"format":"int64","description":"Observation time in Unix nanoseconds, or null when unavailable."}}},"PerpetualMeasureObservation":{"type":"object","additionalProperties":false,"required":["measure_type","value","unit"],"properties":{"measure_type":{"type":"string","minLength":1,"description":"Measure identity, such as open_interest or open_interest_notional."},"value":{"$ref":"#/components/schemas/PerpetualNonNegativeDecimalString"},"unit":{"type":"string","minLength":1,"description":"Explicit venue-native or derived unit, such as base_asset, contracts, or usd."}}},"PerpetualMarketState":{"type":"object","additionalProperties":false,"required":["event_time_ns","status","prices","measures","next_funding_time_ns"],"properties":{"event_time_ns":{"type":"integer","format":"int64","description":"Market-state event time in Unix nanoseconds."},"status":{"type":"string","enum":["active","inactive","delisted","unknown"]},"prices":{"type":"array","items":{"$ref":"#/components/schemas/PerpetualPriceObservation"}},"measures":{"type":"array","items":{"$ref":"#/components/schemas/PerpetualMeasureObservation"}},"next_funding_time_ns":{"type":["integer","null"],"format":"int64","description":"Next funding time in Unix nanoseconds, or null when unavailable."}}},"PerpetualPublicTrade":{"type":"object","additionalProperties":false,"required":["trade_id","price","quantity","quantity_unit","side","event_time_ns"],"properties":{"trade_id":{"type":"string","description":"Venue-native public trade identifier."},"price":{"$ref":"#/components/schemas/PerpetualPositiveDecimalString","description":"Direct venue execution price; never a 0–100 probability."},"quantity":{"$ref":"#/components/schemas/PerpetualPositiveDecimalString","description":"Exact quantity in `quantity_unit`."},"quantity_unit":{"type":"string","enum":["base_asset","contracts"]},"side":{"type":"string","enum":["buy","sell"]},"event_time_ns":{"type":"integer","format":"int64","description":"Venue trade time in Unix nanoseconds."}}},"PerpetualTradeCandle":{"type":"object","additionalProperties":false,"required":["interval","interval_start_ns","interval_end_ns","open","high","low","close","native_volume","native_volume_unit","trade_count","finality"],"properties":{"interval":{"type":"string","description":"Venue candle interval. The preview currently requests one minute.","example":"1m"},"interval_start_ns":{"type":"integer","format":"int64","description":"Inclusive start of the candle's half-open interval, in Unix nanoseconds."},"interval_end_ns":{"type":"integer","format":"int64","description":"Exclusive end of the candle's half-open interval, in Unix nanoseconds."},"open":{"$ref":"#/components/schemas/PerpetualPositiveDecimalString"},"high":{"$ref":"#/components/schemas/PerpetualPositiveDecimalString"},"low":{"$ref":"#/components/schemas/PerpetualPositiveDecimalString"},"close":{"$ref":"#/components/schemas/PerpetualPositiveDecimalString"},"native_volume":{"$ref":"#/components/schemas/PerpetualNonNegativeDecimalString","description":"Exact volume in this candle's `native_volume_unit`; no cross-venue\nnotional is implied. This unit is independent of\n`instrument_context.quantity_unit`.\n"},"native_volume_unit":{"type":"string","enum":["base_asset","contracts"],"description":"Native unit reported by the candle source. It may differ from the\nbook and trade quantity unit for the same instrument.\n"},"trade_count":{"type":["integer","null"],"description":"Venue-reported trade count, or null when unavailable."},"finality":{"type":"string","enum":["open","closed_unconfirmed"],"description":"`open` means the interval has not closed. `closed_unconfirmed` means\nits half-open interval has ended, but the preview does not claim an\nimmutable or venue-confirmed final candle.\n"}}},"PerpetualFundingRate":{"type":"object","additionalProperties":false,"required":["rate","phase","effective_time_ns","calculated_time_ns","rate_period_seconds","payment_interval_seconds","sign_convention","funding_price","funding_price_type"],"properties":{"rate":{"$ref":"#/components/schemas/PerpetualExactDecimalString","description":"Exact signed funding rate in the venue's reported period convention.\nDo not infer percentage scaling or annualize without the period fields.\n"},"phase":{"type":"string","enum":["estimate","final"]},"effective_time_ns":{"type":"integer","format":"int64"},"calculated_time_ns":{"type":["integer","null"],"format":"int64","description":"Calculation time in Unix nanoseconds, or null when unavailable."},"rate_period_seconds":{"type":["integer","null"],"description":"Period represented by `rate`, or null when the venue does not provide it."},"payment_interval_seconds":{"type":["integer","null"],"description":"Funding payment interval, or null when unavailable."},"sign_convention":{"type":"string","const":"positive_longs_pay"},"funding_price":{"anyOf":[{"$ref":"#/components/schemas/PerpetualPositiveDecimalString"},{"type":"null"}],"description":"Venue funding price, or null when unavailable."},"funding_price_type":{"type":["string","null"],"description":"Identity of `funding_price`, such as mark, or null with no price."}}},"PerpetualSnapshot":{"type":"object","additionalProperties":false,"description":"Live perpetual snapshot, in beta. Prices are direct venue prices.\nQuantities retain native units. This object intentionally carries no\nsynthesized notional: consumers must inspect `instrument_context` and\nmust not convert contract quantities when `contract_multiplier` is null.\n","required":["venue","environment","integration_id","canonical_instrument_id","venue_instrument_id","instrument_context","book","market_state","trades","candles","funding","source","fetched_at_ns"],"properties":{"venue":{"type":"string","enum":["hyperliquid","polymarket_perps","kalshi_margin"]},"environment":{"type":"string","minLength":1,"description":"Venue environment for this source identity."},"integration_id":{"type":"string","minLength":1,"description":"Kairos integration and routing identity."},"canonical_instrument_id":{"type":"string","minLength":1,"description":"Stable Kairos perpetual instrument identity."},"venue_instrument_id":{"type":"string","description":"Exact venue-native instrument identifier."},"instrument_context":{"$ref":"#/components/schemas/PerpetualInstrumentContext"},"book":{"$ref":"#/components/schemas/PerpetualOrderBookSnapshot"},"market_state":{"$ref":"#/components/schemas/PerpetualMarketState"},"trades":{"type":"array","items":{"$ref":"#/components/schemas/PerpetualPublicTrade"},"description":"Recent public trades in venue-native quantity units."},"candles":{"type":"array","items":{"$ref":"#/components/schemas/PerpetualTradeCandle"},"description":"One-minute trade candles in venue-native volume units."},"funding":{"type":"array","items":{"$ref":"#/components/schemas/PerpetualFundingRate"},"description":"Final or estimated funding observations using positive_longs_pay sign convention."},"source":{"type":"string","const":"venue_public_api"},"fetched_at_ns":{"type":"integer","format":"int64","description":"Kairos fetch completion time in Unix nanoseconds."}}},"TickRange":{"type":"object","description":"One tick band. `step` applies for prices in `[start, end)`; the last band of a grid is inclusive of `end`. Decimal strings on the 0–1 probability scale.","required":["start","end","step"],"properties":{"start":{"type":"string","example":"0.04"},"end":{"type":"string","example":"0.96"},"step":{"type":"string","example":"0.01"}}},"TickGrid":{"type":"object","description":"A market's current valid tick grid. Wire-compatible with the Data API `GET /markets/tick-size` response; `as_of` is additive.","required":["provider","contract_id","asset_id","ranges","min_tick","source","synthetic","price_level_structure","as_of"],"properties":{"provider":{"type":"string","enum":["kalshi","polymarket"]},"contract_id":{"type":"string"},"asset_id":{"type":["string","null"],"description":"The Polymarket token id the grid was read for; null when the lookup was market-level."},"ranges":{"type":"array","items":{"$ref":"#/components/schemas/TickRange"}},"min_tick":{"type":"string","description":"The finest step across all bands — the smallest increment the market will ever accept.","example":"0.001"},"source":{"type":"string","enum":["metadata_cache","kalshi_price_ranges","streamer_projection"],"description":"Which upstream fact the grid was built from. `streamer_projection` is the orderbook streamer's projected finest venue step, used for a Kalshi market whose live band layout is not currently cached."},"synthetic":{"type":"boolean","description":"True when the grid is a single flat band standing in for a layout that is not currently known — the minimum tick is correct, the band boundaries are not described."},"price_level_structure":{"type":["string","null"],"description":"Kalshi's `price_level_structure` when present; null otherwise."},"as_of":{"type":"string","format":"date-time","description":"When this grid was read from the market metadata cache (UTC). The cache itself is kept current by the orderbook streamer's live tick-change ingestion."}}},"TickGridUnsupported":{"type":"object","description":"Explicit \"no per-market grid\" answer for predictfun. No grid is fabricated.","required":["provider","contract_id","supported","reason"],"properties":{"provider":{"type":"string","enum":["predictfun"]},"contract_id":{"type":"string"},"supported":{"type":"boolean","enum":[false]},"reason":{"type":"string"}}},"TickGridBatchRequest":{"type":"object","required":["provider","items"],"properties":{"provider":{"type":"string","enum":["kalshi","polymarket","predictfun"]},"items":{"type":"array","minItems":1,"maxItems":200,"items":{"type":"object","required":["contract_id"],"properties":{"contract_id":{"type":"string"},"asset_id":{"type":"string","description":"Polymarket token id; optional."}}}}}},"TickGridBatchItemError":{"type":"object","description":"A per-item failure inside a batch. `error.code` matches what the single route would have answered.","required":["contract_id","asset_id","error"],"properties":{"contract_id":{"type":"string"},"asset_id":{"type":["string","null"]},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","example":"not_found"},"message":{"type":"string"}}}}},"TickGridBatchResponse":{"type":"object","required":["provider","results"],"properties":{"provider":{"type":"string"},"results":{"type":"array","description":"Positional — `results[i]` answers `items[i]`.","items":{"oneOf":[{"$ref":"#/components/schemas/TickGrid"},{"$ref":"#/components/schemas/TickGridUnsupported"},{"$ref":"#/components/schemas/TickGridBatchItemError"}]}}}},"ErrorResponse":{"type":"object","description":"Canonical error envelope emitted by every Market Data API endpoint.","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code.","example":"invalid_request"},"message":{"type":"string","description":"Human-readable detail.","example":"market_ids exceeds maximum 200"}}}}},"MarketOutcome":{"type":"object","description":"One outcome/token entry within a market.","required":["outcome","normalized_outcome","token_id"],"properties":{"outcome":{"type":"string","description":"Display-layer outcome label as provided by the venue. For kalshi, this can legitimately be duplicated across YES/NO (it's a team/threshold subtitle, not a binary identity) — use `outcome_index`/`side` for execution logic on those markets.","example":"Yes"},"normalized_outcome":{"type":"string","description":"Lowercased/normalized form of `outcome`.","example":"yes"},"token_id":{"type":"string","description":"Venue-specific outcome/token identifier.","example":"18812649149814341758733697580460697418474693998558159483117"},"outcome_index":{"type":"integer","description":"Stable binary position (0 or 1). Only present for providers keyed directly by market id (e.g. kalshi), and only for the first two outcome entries.","example":0},"side":{"type":"string","enum":["yes","no"],"description":"Stable binary side matching `outcome_index`. Same provider scope as `outcome_index`.","example":"yes"}}},"Market":{"type":"object","description":"Full market metadata document. Resolution-state fields (`resolution_status`, `payout_numerators`, `resolved_ts`, `proposed_price`, `challenge_window_ends_at`) are present in the payload only when set — the overwhelming majority of markets are unresolved and omit all five.","required":["exchange_id","market_id","condition_id","event_id","title","neg_risk","outcomes","raw"],"properties":{"exchange_id":{"type":"string","description":"Canonical provider name.","example":"polymarket"},"market_id":{"type":"string","example":"1897040"},"condition_id":{"type":"string","description":"On-chain condition id for CTF venues; empty string for kalshi.","example":"0xabc123..."},"event_id":{"type":"string","description":"Grouping event id (multi-market events); may be empty.","example":"evt-4521"},"title":{"type":"string","example":"Will BTC close above $120k on July 31?"},"neg_risk":{"type":"boolean","description":"Whether this market is part of a Polymarket negative-risk group.","example":false},"tick_size":{"type":["number","null"],"description":"Minimum price increment (0, 1) exclusive.","example":0.01},"taker_base_fee_bps":{"type":["integer","null"],"example":200},"fees_enabled":{"type":["boolean","null"]},"category":{"type":["string","null"],"example":"Crypto"},"group_slug":{"type":["string","null"],"example":"btc-price-2026"},"fee_type":{"type":["string","null"],"example":"standard"},"status":{"type":["string","null"],"description":"Venue-reported market status, e.g. active/closed. Populated inconsistently across providers.","example":"active"},"image":{"type":["string","null"],"description":"Display image URL."},"icon":{"type":["string","null"],"description":"Display icon URL."},"end_date":{"type":["string","null"],"description":"ISO8601 UTC market end/close time.","example":"2026-07-31T23:59:59Z"},"open_time":{"type":["string","null"],"description":"ISO8601 UTC market open/listing time.","example":"2026-01-01T00:00:00Z"},"resolution_status":{"type":"string","description":"Present only when the market has resolution state.","example":"resolved"},"payout_numerators":{"type":"array","items":{"type":"integer","format":"uint64"},"description":"Present only when non-empty (i.e. the market has resolved with a payout vector).","example":[1,0]},"resolved_ts":{"type":"string","description":"ISO8601 UTC. Present only when set.","example":"2026-07-16T00:00:00Z"},"proposed_price":{"type":"number","description":"Present only when set (a resolution price has been proposed).","example":1},"challenge_window_ends_at":{"type":"string","description":"ISO8601 UTC. Present only when set.","example":"2026-07-16T02:00:00Z"},"outcomes":{"type":"array","items":{"$ref":"#/components/schemas/MarketOutcome"}},"raw":{"type":"object","description":"Full, unmodified provider-specific market document. Shape varies by provider. Treat as opaque/pass-through."}}},"MarketBatchRequest":{"type":"object","description":"Request body for POST /v1/markets/batch.","required":["provider","market_ids"],"properties":{"provider":{"type":"string","enum":["kalshi","polymarket","opinion","predictfun","hyperliquid","kalshi_offchain","dome"]},"market_ids":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":200,"description":"Up to 200 market ids for the given provider."}}},"MarketBatchResponse":{"type":"object","description":"Response body for POST /v1/markets/batch.","required":["exchange_id","markets","misses"],"properties":{"exchange_id":{"type":"string","example":"polymarket"},"markets":{"type":"object","description":"Map of market_id → Market, for every requested id that was found.","additionalProperties":{"$ref":"#/components/schemas/Market"}},"misses":{"type":"array","items":{"type":"string"},"description":"market_ids from the request that were not found."}}},"MarketIdentifierResolveRequestItem":{"type":"object","required":["provider","identifier","scope"],"properties":{"provider":{"type":"string","enum":["kalshi","polymarket","opinion","predictfun","hyperliquid","kalshi_offchain","dome"]},"identifier":{"type":"string","description":"Venue-specific identifier to resolve. Must be non-empty after trimming.","example":"18812649149814341758733697580460697418474693998558159483117"},"scope":{"type":"string","enum":["market","outcome"],"description":"`market` resolves via the venue's market id/ticker/condition-like identifier; `outcome` resolves via an outcome/token id. No other scope values are accepted (case-insensitive on input)."}}},"MarketIdentifierResolveRequestBody":{"type":"object","required":["requests"],"properties":{"requests":{"type":"array","items":{"$ref":"#/components/schemas/MarketIdentifierResolveRequestItem"},"minItems":1,"maxItems":200}}},"MarketIdentifierResolveResult":{"type":"object","required":["provider","identifier","scope","found","market_id"],"properties":{"provider":{"type":"string","description":"Provider exactly as submitted in the request (not canonicalized).","example":"kalshi_offchain"},"identifier":{"type":"string"},"scope":{"type":"string","enum":["market","outcome"]},"found":{"type":"boolean"},"market_id":{"type":["string","null"],"description":"Canonical Kairos market_id, or null when unresolved."}}},"MarketIdentifierResolveResponse":{"type":"object","required":["results"],"properties":{"results":{"type":"array","description":"Same length and order as the request's `requests` array.","items":{"$ref":"#/components/schemas/MarketIdentifierResolveResult"}}}},"MarketListResponse":{"type":"object","description":"Response body for GET /v1/markets.","required":["exchange_id","markets","count","next_cursor","has_more"],"properties":{"exchange_id":{"type":"string","example":"kalshi"},"markets":{"type":"array","items":{"$ref":"#/components/schemas/Market"}},"count":{"type":"integer","description":"Number of markets in this page (`markets.length`).","example":100},"next_cursor":{"type":["string","null"],"description":"Opaque cursor for the next page, or null when this is the last page."},"has_more":{"type":"boolean"}}},"ResolutionsResponse":{"type":"object","description":"Map of market_id → resolved YES-side fraction (0–1). Markets with no resolution yet are omitted entirely — there is no `null`/`false` placeholder for \"unresolved\".","required":["resolutions"],"properties":{"resolutions":{"type":"object","additionalProperties":{"type":"number","minimum":0,"maximum":1},"example":{"KXBTC-26JUL":1,"KXETH-26JUL":0.5}}}},"ResolutionStatus":{"type":"object","description":"One-row resolution lifecycle snapshot for a single market.","required":["provider","market_id","condition_id","status","proposed_price","proposed_at","challenge_window_ends_at","proposer","disputer","dispute_count","reset_count","payout_numerators","resolved_ts","last_event_ts"],"properties":{"provider":{"type":"string","description":"Canonical provider name.","example":"polymarket"},"market_id":{"type":"string","description":"Echoes the requested market_id/ticker.","example":"516710"},"condition_id":{"type":"string","description":"On-chain condition id (CTF venues); empty string for kalshi.","example":"0xcond123"},"status":{"type":"string","description":"Lifecycle status. Observed values include `proposed`, `disputed`, and `resolved`; not an exhaustive enum.","example":"proposed"},"proposed_price":{"type":["number","null"],"description":"Normalized proposed settlement price, or null if none proposed yet.","example":0.5},"proposed_at":{"type":["string","null"],"description":"ISO8601 UTC.","example":"2026-07-15T09:30:00+00:00"},"challenge_window_ends_at":{"type":["string","null"],"description":"ISO8601 UTC. Null until a proposal starts the challenge window."},"proposer":{"type":"string","description":"Proposer address; empty string if none.","example":"0xprop"},"disputer":{"type":"string","description":"Disputer address; empty string if undisputed."},"dispute_count":{"type":"integer","example":1},"reset_count":{"type":"integer","example":0},"payout_numerators":{"type":"array","items":{"type":"integer","format":"uint64"},"description":"Empty array until resolved."},"resolved_ts":{"type":["string","null"],"description":"ISO8601 UTC. Null until resolved."},"last_event_ts":{"type":"string","description":"ISO8601 UTC timestamp of the most recent lifecycle event.","example":"2026-07-15T09:30:00+00:00"}}},"ResolutionEvent":{"type":"object","description":"One entry in a market's resolution lifecycle timeline. Optional fields are omitted from the JSON entirely when unset/empty/zero, matching the codebase's omit-empty wire style — they are NOT emitted as null.","required":["event_type","source","event_ts"],"properties":{"event_type":{"type":"string","description":"e.g. proposed, disputed, reset, settled_offchain.","example":"proposed"},"source":{"type":"string","description":"Origin of the event, e.g. polygon (on-chain UMA) or kalshi_api.","example":"polygon"},"event_ts":{"type":"string","description":"ISO8601 UTC.","example":"2026-07-01T10:00:00+00:00"},"price_norm":{"type":"number","description":"Normalized price associated with this event, when present.","example":1},"too_early":{"type":"boolean","description":"Only present (and true) when the UMA \"too early\" flag was set."},"payout_numerators":{"type":"array","items":{"type":"integer","format":"uint64"},"description":"Only present when non-empty.","example":[1,0]},"proposer":{"type":"string","example":"0xprop"},"disputer":{"type":"string"},"bond":{"type":"string","description":"Raw on-chain bond amount (base-unit decimal string, not float — avoids precision loss).","example":"500000000000"},"reward":{"type":"string","description":"Raw on-chain reward amount (base-unit decimal string).","example":"5000000"},"expiration_ts":{"type":"string","description":"ISO8601 UTC.","example":"2026-07-01T12:00:00+00:00"},"request_timestamp":{"type":"string","description":"ISO8601 UTC.","example":"2026-07-01T09:00:00+00:00"},"tx_hash":{"type":"string","example":"0xdead"},"block_number":{"type":"integer","format":"uint64","description":"Only present when non-zero.","example":123}}},"ResolutionEventsResponse":{"type":"object","required":["provider","market_id","market_key","events"],"properties":{"provider":{"type":"string","example":"kalshi"},"market_id":{"type":"string","description":"Echoes the requested market_id.","example":"TICK-A"},"market_key":{"type":"string","description":"The key events were actually queried by — equals market_id for kalshi; equals the resolved condition_id for CTF venues.","example":"TICK-A"},"events":{"type":"array","description":"Chronological, oldest first. Capped at 200 events.","items":{"$ref":"#/components/schemas/ResolutionEvent"}}}},"Mark":{"type":"object","required":["contract_id","token_id","price"],"properties":{"contract_id":{"type":"string","example":"0xabc123"},"token_id":{"type":"string","example":"18812649149814341758733697580460697418474693998558159483117"},"price":{"type":"number","description":"Last trade price on the 0–100 scale (matches candles/trades; divide by 100 for a 0–1 probability).","example":63.5}}},"MarksResponse":{"type":"object","required":["marks"],"properties":{"marks":{"type":"array","description":"One entry per pair that has ever traded, in request order. Never-traded pairs are omitted.","items":{"$ref":"#/components/schemas/Mark"}}}},"CandleObject":{"type":"object","description":"One OHLCV bucket. Prices are on a 0-100 scale (percentage probability).","required":["contract_id","timeframe_seconds","bucket_start","open","high","low","close","volume"],"properties":{"contract_id":{"type":"string","description":"Echo of the request's `contract_id` (the caller-supplied id, not the resolved canonical market id).","example":"21742633143463906290569050155826241533067272736897614950488156847949938836455"},"timeframe_seconds":{"type":"integer","enum":[1,60,300,900,3600,14400,86400],"description":"Bucket width in seconds, echoing the request.","example":3600},"bucket_start":{"type":"string","description":"Bucket start timestamp, UTC, always rendered with an explicit `+00:00` offset (not `Z`).","example":"2026-07-15T00:00:00+00:00"},"open":{"type":"number","format":"double","minimum":0,"maximum":100,"description":"Opening price (0-100 scale).","example":61.5},"high":{"type":"number","format":"double","minimum":0,"maximum":100,"description":"High price in the bucket (0-100 scale).","example":63},"low":{"type":"number","format":"double","minimum":0,"maximum":100,"description":"Low price in the bucket (0-100 scale).","example":60.8},"close":{"type":"number","format":"double","minimum":0,"maximum":100,"description":"Closing price (0-100 scale).","example":62.4},"volume":{"type":"integer","format":"int64","description":"Traded volume in the bucket, provider-native units.","example":184230},"token_id":{"type":"string","description":"Resolved per-outcome CLOB token id. Omitted entirely from the JSON object when empty.","example":"704721957297303853272349184"}}},"CandleListResponse":{"type":"object","description":"Response body of `GET /v1/candles` in JSON mode.","required":["candles"],"properties":{"candles":{"type":"array","items":{"$ref":"#/components/schemas/CandleObject"},"description":"Candles in ascending `bucket_start` order. Empty when the aligned window is authoritatively empty."}}},"CandleBatchItem":{"type":"object","description":"One series request inside a batch. Same validation rules as the `GET /v1/candles` query parameters.","required":["provider","contract_id","timeframe_seconds","start","end"],"properties":{"provider":{"type":"string","enum":["kalshi","polymarket","opinion","predictfun","hyperliquid","dome","kalshi_offchain"],"example":"polymarket"},"contract_id":{"type":"string","maxLength":128,"example":"21742633143463906290569050155826241533067272736897614950488156847949938836455"},"timeframe_seconds":{"description":"Bucket width in seconds. Accepted as a JSON number or a numeric string. Valid values 1, 60, 300, 900, 3600, 14400, 86400.","oneOf":[{"type":"integer"},{"type":"string"}],"example":3600},"start":{"type":"string","description":"Same accepted formats as `GET /v1/candles`'s `start` parameter (RFC 3339, bare datetime, or bare date).","example":"2026-07-15T00:00:00Z"},"end":{"type":"string","description":"Same accepted formats as `GET /v1/candles`'s `end` parameter.","example":"2026-07-16T00:00:00Z"},"outcome":{"description":"Zero-based outcome index. Accepted as a JSON number or a numeric string. Defaults to 0.","oneOf":[{"type":"integer"},{"type":"string"}],"default":0,"example":0}}},"CandleBatchRequest":{"type":"object","description":"Request body of `POST /v1/candles/batch`. Provide either `requests` or the legacy `items` alias — if `requests` is non-empty it takes precedence, otherwise `items` is used. At least one entry is required across the two fields, and the effective array may not exceed 200 entries.\n","properties":{"requests":{"type":"array","items":{"$ref":"#/components/schemas/CandleBatchItem"},"maxItems":200},"items":{"type":"array","items":{"$ref":"#/components/schemas/CandleBatchItem"},"maxItems":200,"description":"Legacy alias for `requests`, used only when `requests` is absent or empty."}}},"CandleBatchResult":{"type":"object","description":"Result for one input item, at the same array position as the request.","required":["index","candles"],"properties":{"index":{"type":"integer","description":"Zero-based position in the input `requests`/`items` array.","example":0},"candles":{"type":"array","items":{"$ref":"#/components/schemas/CandleObject"},"description":"Empty when the item's window was authoritatively empty OR when the item errored (check `error`)."},"error":{"type":"string","description":"Present only when this item failed validation or fetch. When present, `candles` is always `[]`.","example":"unknown provider \"acme\""}}},"CandleBatchResponse":{"type":"object","description":"Response body of `POST /v1/candles/batch` in JSON mode.","required":["results"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/CandleBatchResult"}}}},"CandleBinaryFrame":{"type":"string","format":"binary","description":"Columnar binary candle frame (`Content-Type: application/x-kairos-candles`), returned by both candle endpoints when negotiated via `?fmt=binary` or an `Accept: application/x-kairos-candles` header. Byte layout (little-endian): `u8 magic=0xCA, u8 version=1, u16 num_results`, then for each section `u32 index, u32 count`, followed by six columnar arrays of length `count`: `u32[] t` (bucket start, epoch seconds), `u16[] o,h,l,c` (prices scaled ×100 of the 0-100 float, clamped to [0, 10000]), and `i64[] vol` (volume ×100). In a batch frame, a failed item sets the high bit (`0x80000000`) of its section's `count` — mask it off before using the count, and treat a set bit as \"this series errored\" rather than \"no data\". The error message itself is only available in the JSON response.\n"},"TradeRecord":{"type":"object","description":"A single deduplicated trade row. `token_id`, `metadata`, `taker_address`, and `side` are omitted entirely from the JSON (not emitted as null/empty) when the underlying value is empty — `metadata` is additionally omitted when it equals the default `\"{}\"`.\n","required":["trade_id","contract_id","size","price","outcome","timestamp"],"properties":{"trade_id":{"type":"string","description":"Venue-scoped trade identifier (part of the dedup key together with provider_id and contract_id).","example":"0x9f2ac3f1b0e2f4a9c8d7b6a5f4e3d2c1b0a9f8e7"},"contract_id":{"type":"string","description":"Echoes the requested contract_id.","example":"0x8b1c119419f622e21fc576ec8e9c2c07b2c1b09babf0e1f3d1cb9bfc1a8f9c2"},"size":{"type":"integer","format":"int64","description":"Trade size, truncated to an integer.","example":250},"price":{"type":"number","description":"Trade price on the **0–100 cents scale, for every provider** (never a 0–1 probability, never already-divided dollars). May include sub-cent fractions on venues with sub-penny ticks (e.g. Kalshi). USD notional = size * price / 100.\n","minimum":0,"maximum":100,"example":62.5},"outcome":{"type":"string","description":"Outcome label as stored on the trade row (venue-dependent free text, e.g. \"Yes\"/\"No\").","example":"Yes"},"timestamp":{"type":"number","description":"Unix timestamp in seconds, with fractional (millisecond) precision preserved.","example":1737479998.412},"token_id":{"type":"string","description":"Venue outcome-token identifier, when the venue is token-keyed (e.g. Polymarket CTF token id). Omitted when empty.","example":"109451234567890332112345678903321"},"metadata":{"type":"string","description":"Raw JSON-encoded metadata blob from the ingestion pipeline, passed through as a string (not parsed). Omitted when empty or the literal \"{}\".","example":"{\"maker_order_id\":\"0xabc\"}"},"taker_address":{"type":"string","description":"On-chain taker address, when known. Omitted when empty.","example":"0xabc1234567890abcdef1234567890abcdef1234"},"side":{"type":"string","description":"Canonical trade side as stored on the row. Omitted when empty.","example":"buy"}}},"TradeHistoryResponse":{"type":"object","required":["trades","has_more","oldest_available_ts","coverage_hours"],"properties":{"trades":{"type":"array","description":"Trades in the resolved window, newest first, capped at `limit` entries.","items":{"$ref":"#/components/schemas/TradeRecord"}},"has_more":{"type":"boolean","description":"True when the number of trades returned equals the requested `limit`, meaning more trades likely exist beyond this page.\n","example":true},"oldest_available_ts":{"type":["number","null"],"description":"Unix timestamp (seconds, fractional) of the oldest trade in the returned page, or null when `trades` is empty. Reflects the oldest trade *in this response*, not necessarily the oldest trade ever recorded for the contract.\n","example":1737479950.001},"coverage_hours":{"type":"number","description":"Hours between `oldest_available_ts` and the request time, rounded to 1 decimal place. 0.0 when `trades` is empty.\n","example":0.01}}},"TradeMetrics":{"type":"object","required":["volume_usd","outcome_0_volume_usd","outcome_1_volume_usd","outcome_0_volume_share_pct","trade_count","window_seconds","coverage_pct"],"properties":{"volume_usd":{"type":"number","description":"Total USD notional traded in the window (size * price / 100, summed), rounded to 2 decimals.","example":184032.55},"outcome_0_volume_usd":{"type":"number","description":"USD volume attributed to \"outcome 0\". For Kalshi this is the \"yes\" side; for all other providers it is whichever token had the highest volume in the window (not guaranteed to be \"Yes\"). Rounded to 2 decimals.\n","example":121004.1},"outcome_1_volume_usd":{"type":"number","description":"USD volume attributed to \"outcome 1\". For Kalshi this is the \"no\" side; for all other providers it is the second-highest-volume token in the window. Rounded to 2 decimals.\n","example":63028.45},"outcome_0_volume_share_pct":{"type":"number","description":"outcome_0_volume_usd as a percentage of (outcome_0 + outcome_1) volume, rounded to 1 decimal. Defaults to 50.0 when both outcome volumes are zero.\n","minimum":0,"maximum":100,"example":65.8},"trade_count":{"type":"integer","format":"int64","description":"Number of deduplicated trades in the window.","example":5123},"window_seconds":{"type":"integer","description":"Echoes the effective `window_seconds` request parameter.","example":3600},"coverage_pct":{"type":"number","description":"Percentage of the requested window actually covered by data, clamped to [0, 100]. 0 when no trades are found. Rounded to 1 decimal.\n","example":97.2}}},"TradeMetricsResponse":{"type":"object","required":["metrics"],"properties":{"metrics":{"$ref":"#/components/schemas/TradeMetrics"}}}}}}}]}