{
  "asyncapi": "3.0.0",
  "info": {
    "title": "Polymarket PolyBolt WebSocket",
    "version": "2.0.0",
    "description": "Real-time push socket for Polymarket market prices and licensed reference prices. One connection carries many channel subscriptions. Every server frame is a uniform envelope with a dense per-channel sequence, a snapshot barrier on subscribe, and typed close codes. Replaces the legacy Real-Time Data Service (RTDS)."
  },
  "servers": {
    "production": {
      "host": "ws-live-v2.polymarket.com",
      "pathname": "/ws",
      "protocol": "wss",
      "description": "Production socket. The same host serves the Polymarket web app's ticket endpoint; third-party clients authenticate gated channels with an Access Token or CLOB API credentials over the socket itself."
    }
  },
  "channels": {
    "ws": {
      "address": "/ws",
      "title": "Live Data Channel",
      "description": "Send JSON text frames tagged with op. The server answers every op with an ack or an error ack (never a close, except for policy breaches, close 4008, and an expired Access Token, close 1008) and pushes envelopes for subscribed channels. Limits per connection: 64 active (channel, filter) subscriptions, 20 subscribe or unsubscribe frames per second (a batch frame counts once), 64 KB per frame, 8 auth attempts. The server sends a WebSocket ping every 25 seconds; two missed pongs close 4002. Close codes: 4001 authentication failed on a presented credential, 4002 slow consumer or pong timeout, 4003 server draining (reconnect once after a uniform 0 to 10 s delay), 4008 policy violation (fix the client; do not reconnect blindly), which also closes a socket that neither passed authentication nor registered a subscription within the server's unengaged timeout (120 seconds by default, reason text unengaged) once that rule is enforced, 1008 authentication expired (a connection authenticated with an Access Token whose last accepted token expired, 30 second leeway included, with no renewal; refresh the token and reconnect).",
      "messages": {
        "subscribe": {
          "$ref": "#/components/messages/subscribe"
        },
        "unsubscribe": {
          "$ref": "#/components/messages/unsubscribe"
        },
        "auth": {
          "$ref": "#/components/messages/auth"
        },
        "ping": {
          "$ref": "#/components/messages/ping"
        },
        "subscribed": {
          "$ref": "#/components/messages/subscribed"
        },
        "unsubscribed": {
          "$ref": "#/components/messages/unsubscribed"
        },
        "authed": {
          "$ref": "#/components/messages/authed"
        },
        "pong": {
          "$ref": "#/components/messages/pong"
        },
        "error": {
          "$ref": "#/components/messages/error"
        },
        "pricePolymarket": {
          "$ref": "#/components/messages/pricePolymarket"
        },
        "priceCrypto": {
          "$ref": "#/components/messages/priceCrypto"
        },
        "priceEquity": {
          "$ref": "#/components/messages/priceEquity"
        },
        "priceCryptoTwap": {
          "$ref": "#/components/messages/priceCryptoTwap"
        },
        "priceEquityTwap": {
          "$ref": "#/components/messages/priceEquityTwap"
        },
        "challenge": {
          "$ref": "#/components/messages/challenge"
        },
        "challengeRequest": {
          "$ref": "#/components/messages/challengeRequest"
        }
      }
    }
  },
  "operations": {
    "subscribe": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/ws"
      },
      "title": "Subscribe",
      "summary": "Add one or more (channel, filter) subscriptions",
      "description": "Batch form: subscriptions is an array of {channel, filter}. Singular form: {op: subscribe, channel, filter}. Each newly added subscription is acknowledged with subscribed, then receives exactly one snapshot envelope (snapshot: true), then live frames. A duplicate (channel, filter) is acknowledged again without a new snapshot. A gated channel on an unauthenticated connection answers the non-closing error code auth_required; other subscriptions on the same connection are unaffected.",
      "messages": [
        {
          "$ref": "#/channels/ws/messages/subscribe"
        }
      ]
    },
    "unsubscribe": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/ws"
      },
      "title": "Unsubscribe",
      "summary": "Remove subscriptions; same singular and batch shapes as subscribe",
      "messages": [
        {
          "$ref": "#/channels/ws/messages/unsubscribe"
        }
      ]
    },
    "auth": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/ws"
      },
      "title": "Authenticate",
      "summary": "Unlock the gated channels on this connection with an Access Token, CLOB API credentials or a web-app ticket",
      "description": "Send before subscribing to price.crypto, price.equity, price.crypto.twap or price.equity.twap. Answered with authed. A refused credential answers the non-closing error code auth_invalid; an unreachable verifier answers auth_unavailable (retry later). Idempotent once authenticated, except on a connection authenticated with an Access Token: there a new Access Token renews the connection. Send it before the current token expires, after refreshing over HTTP (never send the Refresh Token on the socket). The renewal must carry the same sub and client_id; it answers authed with its rid, replaces the expiry deadline and keeps every subscription. A refused renewal answers auth_invalid or auth_unavailable and keeps the previous deadline, and CLOB credentials or a ticket on that connection answer bad_op. The server closes the connection 1008 authentication expired when the last accepted token expires, 30 second leeway included, even if the client sends nothing. Lifetime budget of 8 auth frames per connection, accepted renewals excepted; the ninth closes 4008. The Polymarket web app uses a different credential in the same op (a one-time ticket in the ticket field); third-party clients use the auth object, with CLOB API credentials or an Identity Provider Access Token ({\"accessToken\": ...}). A web-app ticket is minted from a Turnstile solve; when the widget carried this connection's challenge nonce (see the challenge frame) the ticket verifies on this socket alone, and one minted for another socket answers the non-closing auth_expired (mint again).",
      "messages": [
        {
          "$ref": "#/channels/ws/messages/auth"
        }
      ]
    },
    "ping": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/ws"
      },
      "title": "Ping",
      "summary": "Optional application-level liveness probe; answered with pong",
      "description": "Not required for keepalive: the server sends WebSocket ping frames every 25 seconds and standard clients answer them automatically.",
      "messages": [
        {
          "$ref": "#/channels/ws/messages/ping"
        }
      ]
    },
    "acknowledgements": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/ws"
      },
      "title": "Acknowledgements",
      "summary": "challenge (on request), subscribed, unsubscribed, authed, pong and error acks; rid is echoed when the request carried one",
      "messages": [
        {
          "$ref": "#/channels/ws/messages/challenge"
        },
        {
          "$ref": "#/channels/ws/messages/subscribed"
        },
        {
          "$ref": "#/channels/ws/messages/unsubscribed"
        },
        {
          "$ref": "#/channels/ws/messages/authed"
        },
        {
          "$ref": "#/channels/ws/messages/pong"
        },
        {
          "$ref": "#/channels/ws/messages/error"
        }
      ]
    },
    "pricePolymarket": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/ws"
      },
      "title": "price.polymarket",
      "summary": "Best bid and ask per outcome token (public, no auth)",
      "description": "Filter: {asset_id: <outcome token id, decimal string>}. Snapshot payload: the latest best bid and ask for the token, or an empty array [] when the token has not traded within the server's warm window. Live payload: one best-bid-and-ask object per change. Same-token updates are conflated (latest wins) when the client is slow.",
      "messages": [
        {
          "$ref": "#/channels/ws/messages/pricePolymarket"
        }
      ]
    },
    "priceCrypto": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/ws"
      },
      "title": "price.crypto",
      "summary": "Crypto reference prices, about one update per second per symbol (gated)",
      "description": "Filter: {symbol}, lowercase, one of btcusd, ethusd, solusd, dogeusd, xrpusd, bnbusd, hypeusd, zecusd. Snapshot payload: {symbol, source, data: [points for the previous two minutes]}. Live payload: one point. Every payload names the vendor that produced it in source (pyth or chainlink; ownership is a per-symbol operator decision); during a rolling ownership flip old and new pods serve different vendors for the flipped symbol, so consecutive frames can carry different sources until the roll completes.",
      "messages": [
        {
          "$ref": "#/channels/ws/messages/priceCrypto"
        }
      ]
    },
    "priceEquity": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/ws"
      },
      "title": "price.equity",
      "summary": "Pyth equity, ETF, forex, metals and commodity reference prices (gated)",
      "description": "Filter: {symbol}, lowercase. Catalog: stocks aapl, tsla, msft, googl, amzn, meta, nvda, nflx, pltr, open, rklb, abnb, coin, hood; ETFs qqq, spy, ewy, vxx; forex eurusd, gbpusd, usdcad, usdjpy, usdkrw; precious metals xauusd, xagusd; commodities wti, cc, ngd (front-month futures, the symbol stays the root while the contract rolls). Snapshot payload: {symbol, source, data: [points for the previous two minutes]}. Live payload: one point with optional received_at and is_carried_forward. Every payload names the vendor that produced it in source (pyth, massive or chainlink; ownership is a per-symbol operator decision); during a rolling ownership flip old and new pods serve different vendors for the flipped symbol, so consecutive frames can carry different sources until the roll completes. Equity payloads also carry market_status (open, closed or unknown) once a producer states it: the market of the instrument the series prices, per provider, absent when not stated. A status change that repeats the last price arrives as a normal live frame with that price, is_carried_forward true and the new market_status, so a frame with an unchanged timestamp is NOT a duplicate when its market_status changed: never dedup on timestamp. While a series is closed or unknown, its snapshot also carries the last price point when it is older than the two-minute window.",
      "messages": [
        {
          "$ref": "#/channels/ws/messages/priceEquity"
        }
      ]
    },
    "priceCryptoTwap": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/ws"
      },
      "title": "price.crypto.twap",
      "summary": "Chainlink time-weighted average prices used by crypto up/down market resolution (gated)",
      "description": "Filter: {symbol, window_seconds}. Only window_seconds 60 exists; symbols btcusd, ethusd, solusd, xrpusd, dogeusd, hypeusd, bnbusd. Another positive window is accepted but never carries data. Snapshot payload: {symbol, window_seconds, data: [points]}. Live payload: one point including window_seconds.",
      "messages": [
        {
          "$ref": "#/channels/ws/messages/priceCryptoTwap"
        }
      ]
    },
    "priceEquityTwap": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/ws"
      },
      "title": "price.equity.twap",
      "summary": "Chainlink Data Streams FX time-weighted average prices (gated)",
      "description": "Filter: {symbol, window_seconds}. Launch feeds eurusd and usdjpy at window_seconds 60; another positive window is accepted but never carries data. Ticket-gated like the other vendor channels and snapshot-then-live like price.crypto.twap. Snapshot payload: {symbol, window_seconds, source, data: [points]}. Live payload: one point including window_seconds and source. Every payload names the vendor that produced it in source; the channel is Chainlink-only today, so the value is chainlink. Payloads also carry market_status (the FX TWAPs are continuous-hours products and read open), absent when not stated; on this channel it changes only with a new price.",
      "messages": [
        {
          "$ref": "#/channels/ws/messages/priceEquityTwap"
        }
      ]
    },
    "challenge": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/ws"
      },
      "title": "Request the challenge",
      "summary": "Ask for this connection's challenge nonce",
      "description": "Ask for this connection's challenge nonce. The server never pushes it unasked: a client that authenticates with a ticket sends this right after the socket opens, then renders the Turnstile widget with the nonce as cData. Idempotent (the same nonce for the life of the connection, rid echoed) and metered like subscribe and auth. A nonce field in the request is ignored: a client can never choose its own. A server that predates this op answers the non-closing bad_op error ack with no rid (the frame did not parse): treat an uncorrelated bad_op while the request is pending as no nonce served, and render the widget without cData.",
      "messages": [
        {
          "$ref": "#/channels/ws/messages/challengeRequest"
        }
      ]
    }
  },
  "components": {
    "messages": {
      "subscribe": {
        "name": "subscribe",
        "title": "subscribe",
        "payload": {
          "$ref": "#/components/schemas/SubscribeRequest"
        },
        "examples": [
          {
            "name": "batch",
            "summary": "Two channels in one frame",
            "payload": {
              "op": "subscribe",
              "rid": "s1",
              "subscriptions": [
                {
                  "channel": "price.polymarket",
                  "filter": {
                    "asset_id": "21742633143463906290569050155826241533067272736897614950488156847949938836455"
                  }
                },
                {
                  "channel": "price.crypto",
                  "filter": {
                    "symbol": "btcusd"
                  }
                }
              ]
            }
          },
          {
            "name": "singular",
            "summary": "One subscription",
            "payload": {
              "op": "subscribe",
              "channel": "price.crypto.twap",
              "filter": {
                "symbol": "btcusd",
                "window_seconds": 60
              }
            }
          },
          {
            "name": "pinned-provider",
            "summary": "One subscription pinned to a provider (spec 08b; ignored while the selector is disabled)",
            "payload": {
              "op": "subscribe",
              "channel": "price.equity",
              "filter": {
                "symbol": "xauusd",
                "provider": "pyth"
              },
              "rid": "s3"
            }
          }
        ]
      },
      "unsubscribe": {
        "name": "unsubscribe",
        "title": "unsubscribe",
        "payload": {
          "$ref": "#/components/schemas/UnsubscribeRequest"
        },
        "examples": [
          {
            "name": "singular",
            "payload": {
              "op": "unsubscribe",
              "channel": "price.crypto",
              "filter": {
                "symbol": "btcusd"
              }
            }
          }
        ]
      },
      "auth": {
        "name": "auth",
        "title": "auth",
        "payload": {
          "$ref": "#/components/schemas/AuthRequest"
        },
        "examples": [
          {
            "name": "clob-credentials",
            "summary": "CLOB API credentials (third-party clients)",
            "payload": {
              "op": "auth",
              "rid": "a1",
              "auth": {
                "apiKey": "00000000-0000-4000-8000-000000000000",
                "secret": "<api secret>",
                "passphrase": "<api passphrase>"
              }
            }
          },
          {
            "name": "access-token",
            "summary": "Identity Provider Access Token (third-party clients)",
            "payload": {
              "op": "auth",
              "rid": "a3",
              "auth": {
                "accessToken": "<access token>"
              }
            }
          },
          {
            "name": "web-app-ticket",
            "summary": "One-time ticket (the Polymarket web app)",
            "payload": {
              "op": "auth",
              "rid": "a2",
              "ticket": "5c1d2e3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f"
            }
          }
        ]
      },
      "ping": {
        "name": "ping",
        "title": "ping",
        "payload": {
          "$ref": "#/components/schemas/PingRequest"
        },
        "examples": [
          {
            "name": "ping",
            "payload": {
              "op": "ping",
              "rid": "p1"
            }
          }
        ]
      },
      "subscribed": {
        "name": "subscribed",
        "title": "subscribed",
        "payload": {
          "$ref": "#/components/schemas/ChannelAck"
        },
        "examples": [
          {
            "name": "subscribed",
            "payload": {
              "op": "subscribed",
              "channel": "price.crypto",
              "rid": "s1"
            }
          },
          {
            "name": "subscribed-with-provider",
            "summary": "Provider selector enabled: the ack names the SERVED provider",
            "payload": {
              "op": "subscribed",
              "channel": "price.equity",
              "rid": "s2",
              "provider": "chainlink"
            }
          }
        ]
      },
      "unsubscribed": {
        "name": "unsubscribed",
        "title": "unsubscribed",
        "payload": {
          "$ref": "#/components/schemas/ChannelAck"
        },
        "examples": [
          {
            "name": "unsubscribed",
            "payload": {
              "op": "unsubscribed",
              "channel": "price.crypto"
            }
          }
        ]
      },
      "authed": {
        "name": "authed",
        "title": "authed",
        "payload": {
          "$ref": "#/components/schemas/SimpleAck"
        },
        "examples": [
          {
            "name": "authed",
            "payload": {
              "op": "authed",
              "rid": "a1"
            }
          }
        ]
      },
      "pong": {
        "name": "pong",
        "title": "pong",
        "payload": {
          "$ref": "#/components/schemas/SimpleAck"
        },
        "examples": [
          {
            "name": "pong",
            "payload": {
              "op": "pong",
              "rid": "p1"
            }
          }
        ]
      },
      "error": {
        "name": "error",
        "title": "error",
        "payload": {
          "$ref": "#/components/schemas/ErrorAck"
        },
        "examples": [
          {
            "name": "auth_required",
            "summary": "Gated channel subscribed before auth; the connection stays open",
            "payload": {
              "op": "error",
              "code": "auth_required",
              "channel": "price.crypto",
              "rid": "s1"
            }
          },
          {
            "name": "bad_filter",
            "payload": {
              "op": "error",
              "code": "bad_filter",
              "channel": "price.polymarket",
              "rid": "s2"
            }
          }
        ]
      },
      "pricePolymarket": {
        "name": "price.polymarket",
        "title": "price.polymarket envelope",
        "payload": {
          "$ref": "#/components/schemas/PricePolymarketEnvelope"
        },
        "examples": [
          {
            "name": "snapshot",
            "payload": {
              "v": 1,
              "channel": "price.polymarket",
              "seq": 1,
              "ts": 1788973000123,
              "snapshot": true,
              "payload": {
                "market": "0x9deb0baac40648821f96f01339229a422e2f5c877de55dc4dbf981f95a1e709c",
                "asset_id": "21742633143463906290569050155826241533067272736897614950488156847949938836455",
                "best_bid": "0.51",
                "best_ask": "0.53",
                "hash": "3f9c1e7a",
                "timestamp": 1788972999871
              }
            }
          },
          {
            "name": "cold snapshot",
            "summary": "Token with no trade inside the warm window",
            "payload": {
              "v": 1,
              "channel": "price.polymarket",
              "seq": 1,
              "ts": 1788973000123,
              "snapshot": true,
              "payload": []
            }
          },
          {
            "name": "live",
            "payload": {
              "v": 1,
              "channel": "price.polymarket",
              "seq": 2,
              "ts": 1788973004501,
              "payload": {
                "market": "0x9deb0baac40648821f96f01339229a422e2f5c877de55dc4dbf981f95a1e709c",
                "asset_id": "21742633143463906290569050155826241533067272736897614950488156847949938836455",
                "best_bid": "0.52",
                "best_ask": "0.53",
                "hash": "a81d02c4",
                "timestamp": 1788973004498
              }
            }
          }
        ]
      },
      "priceCrypto": {
        "name": "price.crypto",
        "title": "price.crypto envelope",
        "payload": {
          "$ref": "#/components/schemas/PriceCryptoEnvelope"
        },
        "examples": [
          {
            "name": "snapshot",
            "payload": {
              "v": 1,
              "channel": "price.crypto",
              "seq": 1,
              "ts": 1788973000000,
              "snapshot": true,
              "payload": {
                "symbol": "btcusd",
                "source": "pyth",
                "data": [
                  {
                    "timestamp": 1788972880000,
                    "value": 64123.5,
                    "full_accuracy_value": "64123.50000000"
                  },
                  {
                    "timestamp": 1788972881000,
                    "value": 64125.1,
                    "full_accuracy_value": "64125.10000000"
                  }
                ]
              }
            }
          },
          {
            "name": "live",
            "payload": {
              "v": 1,
              "channel": "price.crypto",
              "seq": 2,
              "ts": 1788973001000,
              "payload": {
                "symbol": "btcusd",
                "value": 64126.0,
                "full_accuracy_value": "64126.00000000",
                "timestamp": 1788973001000,
                "source": "pyth"
              }
            }
          }
        ]
      },
      "priceEquity": {
        "name": "price.equity",
        "title": "price.equity envelope",
        "payload": {
          "$ref": "#/components/schemas/PriceEquityEnvelope"
        },
        "examples": [
          {
            "name": "live",
            "payload": {
              "v": 1,
              "channel": "price.equity",
              "seq": 5,
              "ts": 1788973001200,
              "payload": {
                "symbol": "aapl",
                "value": 189.42,
                "full_accuracy_value": "189.4217",
                "timestamp": 1788973001200,
                "received_at": 1788973001207,
                "is_carried_forward": false,
                "source": "pyth"
              }
            }
          },
          {
            "name": "status change",
            "payload": {
              "v": 1,
              "channel": "price.equity",
              "seq": 6,
              "ts": 1788973001200,
              "payload": {
                "symbol": "xauusd",
                "value": 4012.35,
                "full_accuracy_value": "4012.35",
                "timestamp": 1788973001200,
                "received_at": 1788976800000,
                "is_carried_forward": true,
                "market_status": "closed",
                "source": "chainlink"
              }
            }
          }
        ]
      },
      "priceCryptoTwap": {
        "name": "price.crypto.twap",
        "title": "price.crypto.twap envelope",
        "payload": {
          "$ref": "#/components/schemas/PriceCryptoTwapEnvelope"
        },
        "examples": [
          {
            "name": "live",
            "payload": {
              "v": 1,
              "channel": "price.crypto.twap",
              "seq": 3,
              "ts": 1788973002000,
              "payload": {
                "symbol": "btcusd",
                "value": 64120.7,
                "full_accuracy_value": "64120.70000000",
                "timestamp": 1788973002000,
                "window_seconds": 60,
                "source": "chainlink"
              }
            }
          }
        ]
      },
      "priceEquityTwap": {
        "name": "price.equity.twap",
        "title": "price.equity.twap envelope",
        "payload": {
          "$ref": "#/components/schemas/PriceEquityTwapEnvelope"
        },
        "examples": [
          {
            "name": "live",
            "payload": {
              "v": 1,
              "channel": "price.equity.twap",
              "seq": 3,
              "ts": 1788973002000,
              "payload": {
                "symbol": "eurusd",
                "value": 1.0842,
                "full_accuracy_value": "1.08420000",
                "timestamp": 1788973002000,
                "window_seconds": 60,
                "source": "chainlink"
              }
            }
          }
        ]
      },
      "challenge": {
        "name": "challenge",
        "title": "challenge",
        "summary": "This connection's challenge nonce, the answer to a challenge request; never pushed unasked",
        "payload": {
          "$ref": "#/components/schemas/ChallengeAck"
        },
        "examples": [
          {
            "name": "answer",
            "summary": "Answer to a challenge request; the same nonce for the life of the connection, rid echoed",
            "payload": {
              "op": "challenge",
              "nonce": "3f2b6c1e-9a4d-4c2b-8e1f-0a1b2c3d4e5f",
              "rid": "c1"
            }
          }
        ]
      },
      "challengeRequest": {
        "name": "challengeRequest",
        "title": "challenge (request)",
        "summary": "Ask for this connection's challenge nonce",
        "payload": {
          "$ref": "#/components/schemas/ChallengeRequest"
        },
        "examples": [
          {
            "name": "request",
            "payload": {
              "op": "challenge",
              "rid": "c1"
            }
          }
        ]
      }
    },
    "schemas": {
      "SubscriptionItem": {
        "type": "object",
        "required": [
          "channel",
          "filter"
        ],
        "properties": {
          "channel": {
            "type": "string",
            "enum": [
              "price.polymarket",
              "price.crypto",
              "price.equity",
              "price.crypto.twap",
              "price.equity.twap"
            ]
          },
          "filter": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/AssetIdFilter"
              },
              {
                "$ref": "#/components/schemas/SymbolFilter"
              },
              {
                "$ref": "#/components/schemas/TwapFilter"
              }
            ]
          }
        }
      },
      "AssetIdFilter": {
        "type": "object",
        "required": [
          "asset_id"
        ],
        "properties": {
          "asset_id": {
            "type": "string",
            "pattern": "^[0-9]{1,78}$",
            "description": "Outcome token id (clobTokenIds entry), decimal string. Leading zeros are normalized."
          }
        }
      },
      "SymbolFilter": {
        "type": "object",
        "required": [
          "symbol"
        ],
        "properties": {
          "symbol": {
            "type": "string",
            "description": "Lowercase symbol. Matching is case-insensitive."
          },
          "provider": {
            "type": "string",
            "enum": [
              "chainlink",
              "pyth"
            ],
            "description": "Optional provider pin (spec 08b). While the server's provider selector is DISABLED (PRICE_PROVIDER_SELECTOR_ENABLED=false, the default), this field is accepted and IGNORED like any unknown key: the lane's owner is served and the subscribed ack carries no provider. While it is ENABLED, the value must be one of the enum tokens; anything else (including massive, owner, numbers, null) is a bad_filter error ack. A pin the catalog serves gets that provider's stream; a pin it cannot serve falls back to the owner's stream, and the subscribed ack names the provider actually served. Ignored on price.polymarket."
          }
        }
      },
      "TwapFilter": {
        "type": "object",
        "required": [
          "symbol",
          "window_seconds"
        ],
        "properties": {
          "symbol": {
            "type": "string"
          },
          "window_seconds": {
            "type": "integer",
            "minimum": 1,
            "description": "Only 60 carries data today, on price.crypto.twap and price.equity.twap alike."
          },
          "provider": {
            "type": "string",
            "enum": [
              "chainlink",
              "pyth"
            ],
            "description": "Optional provider pin (spec 08b). While the server's provider selector is DISABLED (PRICE_PROVIDER_SELECTOR_ENABLED=false, the default), this field is accepted and IGNORED like any unknown key: the lane's owner is served and the subscribed ack carries no provider. While it is ENABLED, the value must be one of the enum tokens; anything else (including massive, owner, numbers, null) is a bad_filter error ack. A pin the catalog serves gets that provider's stream; a pin it cannot serve falls back to the owner's stream, and the subscribed ack names the provider actually served. Ignored on price.polymarket."
          }
        }
      },
      "SubscribeRequest": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "op",
              "subscriptions"
            ],
            "properties": {
              "op": {
                "const": "subscribe"
              },
              "subscriptions": {
                "type": "array",
                "minItems": 1,
                "items": {
                  "$ref": "#/components/schemas/SubscriptionItem"
                }
              },
              "rid": {
                "type": "string",
                "description": "Client request id, echoed on every ack this frame produces."
              }
            }
          },
          {
            "type": "object",
            "required": [
              "op",
              "channel",
              "filter"
            ],
            "properties": {
              "op": {
                "const": "subscribe"
              },
              "channel": {
                "type": "string"
              },
              "filter": {
                "type": "object"
              },
              "rid": {
                "type": "string"
              }
            }
          }
        ]
      },
      "UnsubscribeRequest": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "op",
              "subscriptions"
            ],
            "properties": {
              "op": {
                "const": "unsubscribe"
              },
              "subscriptions": {
                "type": "array",
                "minItems": 1,
                "items": {
                  "$ref": "#/components/schemas/SubscriptionItem"
                }
              },
              "rid": {
                "type": "string"
              }
            }
          },
          {
            "type": "object",
            "required": [
              "op",
              "channel",
              "filter"
            ],
            "properties": {
              "op": {
                "const": "unsubscribe"
              },
              "channel": {
                "type": "string"
              },
              "filter": {
                "type": "object"
              },
              "rid": {
                "type": "string"
              }
            }
          }
        ]
      },
      "AuthRequest": {
        "type": "object",
        "required": [
          "op"
        ],
        "properties": {
          "op": {
            "const": "auth"
          },
          "auth": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ClobCredentials"
              },
              {
                "$ref": "#/components/schemas/AccessTokenCredential"
              }
            ],
            "description": "CLOB API credentials or an Identity Provider Access Token, never both in one object. An object that mixes the two, or an incomplete set of CLOB credentials, answers the non-closing bad_op."
          },
          "rid": {
            "type": "string"
          },
          "ticket": {
            "type": "string",
            "maxLength": 128,
            "description": "The one-time ticket from POST /api/ws-ticket (the Polymarket web app's credential), consumed by the attempt whatever the outcome. Minted from a Cloudflare Turnstile solve; when the widget was rendered with this connection's challenge nonce as cData, the ticket verifies only on this socket, whatever IP either request came from. A stale, consumed or unknown ticket, or one minted for another socket, answers the non-closing auth_expired: mint again."
          }
        },
        "oneOf": [
          {
            "required": [
              "auth"
            ]
          },
          {
            "required": [
              "ticket"
            ]
          }
        ],
        "description": "Exactly one credential per frame: auth (CLOB API credentials or an Identity Provider Access Token, third-party clients) or ticket (the web app). Zero or two answer the non-closing bad_op."
      },
      "ClobCredentials": {
        "type": "object",
        "required": [
          "apiKey",
          "secret",
          "passphrase"
        ],
        "properties": {
          "apiKey": {
            "type": "string"
          },
          "secret": {
            "type": "string"
          },
          "passphrase": {
            "type": "string"
          }
        },
        "not": {
          "required": [
            "accessToken"
          ]
        },
        "description": "The CLOB L2 API credential trio, the same credentials used for the CLOB user WebSocket. Never combined with accessToken in one object: the server answers bad_op."
      },
      "AccessTokenCredential": {
        "type": "object",
        "required": [
          "accessToken"
        ],
        "properties": {
          "accessToken": {
            "type": "string",
            "maxLength": 4096,
            "description": "An Identity Provider Access Token (third-party clients). Verified locally: RS256 under a key of the identity JWKS, the environment's issuer and audience, not expired. A refused or expired token answers the non-closing auth_invalid; a pod that holds no identity keys answers auth_unavailable (retry later)."
          }
        },
        "not": {
          "anyOf": [
            {
              "required": [
                "apiKey"
              ]
            },
            {
              "required": [
                "secret"
              ]
            },
            {
              "required": [
                "passphrase"
              ]
            }
          ]
        },
        "description": "An Identity Provider Access Token. Never combined with apiKey, secret or passphrase in one object: the server answers bad_op."
      },
      "PingRequest": {
        "type": "object",
        "required": [
          "op"
        ],
        "properties": {
          "op": {
            "const": "ping"
          },
          "rid": {
            "type": "string"
          }
        }
      },
      "ChannelAck": {
        "type": "object",
        "required": [
          "op",
          "channel"
        ],
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "subscribed",
              "unsubscribed"
            ]
          },
          "channel": {
            "type": "string"
          },
          "rid": {
            "type": "string"
          },
          "provider": {
            "type": "string",
            "enum": [
              "pyth",
              "massive",
              "chainlink"
            ],
            "description": "subscribed acks only, vendor channels only, and only while the provider selector is enabled (spec 08b): the provider actually SERVED - the requested pin when the catalog serves it, else the lane's owner (massive is reachable on the omitted path only). Also present on the idempotent duplicate-subscribe ack. Absent everywhere else, including every ack while the selector is disabled."
          }
        }
      },
      "SimpleAck": {
        "type": "object",
        "required": [
          "op"
        ],
        "properties": {
          "op": {
            "type": "string",
            "enum": [
              "authed",
              "pong"
            ]
          },
          "rid": {
            "type": "string"
          }
        }
      },
      "ErrorAck": {
        "type": "object",
        "required": [
          "op",
          "code"
        ],
        "properties": {
          "op": {
            "const": "error"
          },
          "code": {
            "type": "string",
            "enum": [
              "bad_op",
              "bad_channel",
              "bad_filter",
              "sub_limit",
              "rate_limited",
              "auth_required",
              "auth_expired",
              "auth_unavailable",
              "auth_invalid",
              "auth_attempts"
            ],
            "description": "bad_op malformed frame; bad_channel unknown channel; bad_filter missing or invalid filter; sub_limit 65th subscription (followed by close 4008); rate_limited more than 20 subscribe/unsubscribe frames per second (followed by close 4008); auth_required gated channel before auth; auth_expired consumed or unknown ticket (web app lane); auth_unavailable verifier unreachable, retry later; auth_invalid credentials refused; auth_attempts ninth auth frame (followed by close 4008)."
          },
          "channel": {
            "type": "string"
          },
          "rid": {
            "type": "string"
          }
        }
      },
      "BestBidAsk": {
        "type": "object",
        "required": [
          "market",
          "asset_id",
          "best_bid",
          "best_ask",
          "hash",
          "timestamp"
        ],
        "properties": {
          "market": {
            "type": "string",
            "description": "Condition id, 0x-prefixed."
          },
          "asset_id": {
            "type": "string",
            "description": "Outcome token id, decimal string."
          },
          "best_bid": {
            "type": "string",
            "description": "Decimal string price."
          },
          "best_ask": {
            "type": "string",
            "description": "Decimal string price."
          },
          "hash": {
            "type": "string",
            "description": "Order book hash at this change."
          },
          "timestamp": {
            "type": "integer",
            "description": "Order book time, Unix milliseconds."
          }
        }
      },
      "BatchPoint": {
        "type": "object",
        "required": [
          "value",
          "full_accuracy_value",
          "timestamp"
        ],
        "properties": {
          "value": {
            "type": "number",
            "description": "Price as a float. Prefer full_accuracy_value."
          },
          "full_accuracy_value": {
            "type": "string",
            "description": "Exact decimal price as published by the source."
          },
          "timestamp": {
            "type": "integer",
            "description": "Source event time, Unix milliseconds."
          }
        },
        "description": "One historical point of a snapshot batch. Batch points carry NO symbol and NO source: both live once at batch level (the batch object's own fields), matching what the server actually emits - the retired shared point schema wrongly REQUIRED a symbol on every point."
      },
      "CryptoPoint": {
        "type": "object",
        "required": [
          "symbol",
          "value",
          "full_accuracy_value",
          "timestamp",
          "source"
        ],
        "properties": {
          "symbol": {
            "type": "string",
            "description": "Lowercase symbol, for example btcusd."
          },
          "value": {
            "type": "number",
            "description": "Price as a float. Prefer full_accuracy_value."
          },
          "full_accuracy_value": {
            "type": "string",
            "description": "Exact decimal price as published by the source."
          },
          "timestamp": {
            "type": "integer",
            "description": "Source event time, Unix milliseconds."
          },
          "source": {
            "type": "string",
            "enum": [
              "pyth",
              "chainlink"
            ],
            "description": "Vendor that produced this price, on every point this server emits. A per-symbol ownership flip rolls the edge fleet, so mid-roll a symbol's consecutive frames can legitimately carry different sources until the roll completes."
          }
        }
      },
      "EquityPoint": {
        "type": "object",
        "required": [
          "symbol",
          "value",
          "full_accuracy_value",
          "timestamp",
          "source"
        ],
        "properties": {
          "symbol": {
            "type": "string",
            "description": "Lowercase symbol, for example btcusd or aapl."
          },
          "value": {
            "type": "number",
            "description": "Price as a float. Prefer full_accuracy_value."
          },
          "full_accuracy_value": {
            "type": "string",
            "description": "Exact decimal price as published by the source."
          },
          "timestamp": {
            "type": "integer",
            "description": "Source event time, Unix milliseconds."
          },
          "source": {
            "type": "string",
            "enum": [
              "pyth",
              "massive",
              "chainlink"
            ],
            "description": "Vendor that produced this price, on every point this server emits. A per-symbol ownership flip rolls the edge fleet, so mid-roll a symbol's consecutive frames can legitimately carry different sources until the roll completes."
          },
          "received_at": {
            "type": "integer",
            "description": "Ingest receive time, Unix milliseconds. Live frames only."
          },
          "is_carried_forward": {
            "type": "boolean",
            "description": "True when the frame repeats an already-measured price instead of carrying a new one: the upstream redelivered its last measurement (for example a closed session), or the frame is a status change repeating the last price. Live frames only."
          },
          "market_status": {
            "type": "string",
            "enum": [
              "open",
              "closed",
              "unknown"
            ],
            "description": "State of the market of the instrument this series prices, as its producer states it: open, closed, or unknown (the vendor reported it cannot determine it). Absent means not stated, never open. It describes each provider's own instrument: a continuous-hours product (the Chainlink 24/7 streams) reads open outside exchange sessions while an exchange-session feed of the same ticker reads closed, and both are truthful. Every frame renders the series' current status. A status change that repeats the last price arrives as a frame carrying that price (its timestamp unchanged), is_carried_forward true and the new market_status: it is not a duplicate, so never dedup on timestamp alone."
          }
        }
      },
      "TwapPoint": {
        "type": "object",
        "required": [
          "symbol",
          "value",
          "full_accuracy_value",
          "timestamp",
          "window_seconds",
          "source"
        ],
        "properties": {
          "symbol": {
            "type": "string",
            "description": "Lowercase symbol, for example btcusd."
          },
          "value": {
            "type": "number",
            "description": "Price as a float. Prefer full_accuracy_value."
          },
          "full_accuracy_value": {
            "type": "string",
            "description": "Exact decimal price as published by the source."
          },
          "timestamp": {
            "type": "integer",
            "description": "Source event time, Unix milliseconds."
          },
          "window_seconds": {
            "type": "integer",
            "const": 60,
            "description": "Averaging window of the TWAP series. Only 60 exists today."
          },
          "source": {
            "type": "string",
            "enum": [
              "chainlink"
            ],
            "description": "The producing vendor. price.crypto.twap is a single-provider lane, so the value is the topic constant."
          }
        }
      },
      "EquityTwapPoint": {
        "type": "object",
        "required": [
          "symbol",
          "value",
          "full_accuracy_value",
          "timestamp",
          "window_seconds",
          "source"
        ],
        "properties": {
          "symbol": {
            "type": "string",
            "description": "Lowercase symbol, for example eurusd."
          },
          "value": {
            "type": "number",
            "description": "Price as a float. Prefer full_accuracy_value."
          },
          "full_accuracy_value": {
            "type": "string",
            "description": "Exact decimal price as published by the source."
          },
          "timestamp": {
            "type": "integer",
            "description": "Source event time, Unix milliseconds."
          },
          "window_seconds": {
            "type": "integer",
            "const": 60,
            "description": "Averaging window of the TWAP series. Only 60 exists today."
          },
          "source": {
            "type": "string",
            "enum": [
              "pyth",
              "massive",
              "chainlink"
            ],
            "description": "Vendor that produced this price, on every point this server emits. The channel is Chainlink-only today, so the emitted value is chainlink. Kept as its own schema because this enum is the full vendor set while TwapPoint pins its topic constant."
          },
          "market_status": {
            "type": "string",
            "enum": [
              "open",
              "closed",
              "unknown"
            ],
            "description": "State of the market of the instrument this series prices, as its producer states it; same meaning as on price.equity. Absent means not stated. On this channel it changes only with a new price (a TWAP has one clock). The Chainlink FX TWAPs are continuous-hours products and read open."
          }
        }
      },
      "PricePolymarketEnvelope": {
        "type": "object",
        "required": [
          "v",
          "channel",
          "seq",
          "ts",
          "payload"
        ],
        "properties": {
          "v": {
            "type": "integer",
            "const": 1,
            "description": "Envelope version."
          },
          "channel": {
            "type": "string",
            "const": "price.polymarket"
          },
          "seq": {
            "type": "integer",
            "description": "Dense per-connection, per-channel sequence, assigned at delivery. Resets on every reconnect. Snapshot frames consume a seq."
          },
          "ts": {
            "type": "integer",
            "description": "Producer event time, Unix milliseconds."
          },
          "snapshot": {
            "type": "boolean",
            "description": "Present and true on the one snapshot frame sent after each new subscription. Absent on live frames."
          },
          "dropped": {
            "type": "integer",
            "description": "Number of frames dropped on this channel since the last delivered frame (slow-consumer signal). Absent when zero."
          },
          "payload": {
            "$ref": "#/components/schemas/PricePolymarketPayload"
          }
        },
        "description": "Snapshot payload is a BestBidAsk object or an empty array; live payload is a BestBidAsk object."
      },
      "PricePolymarketPayload": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/BestBidAsk"
          },
          {
            "type": "array",
            "maxItems": 0,
            "description": "Empty snapshot for a cold token."
          }
        ]
      },
      "PriceCryptoEnvelope": {
        "type": "object",
        "required": [
          "v",
          "channel",
          "seq",
          "ts",
          "payload"
        ],
        "properties": {
          "v": {
            "type": "integer",
            "const": 1,
            "description": "Envelope version."
          },
          "channel": {
            "type": "string",
            "const": "price.crypto"
          },
          "seq": {
            "type": "integer",
            "description": "Dense per-connection, per-channel sequence, assigned at delivery. Resets on every reconnect. Snapshot frames consume a seq."
          },
          "ts": {
            "type": "integer",
            "description": "Producer event time, Unix milliseconds."
          },
          "snapshot": {
            "type": "boolean",
            "description": "Present and true on the one snapshot frame sent after each new subscription. Absent on live frames."
          },
          "dropped": {
            "type": "integer",
            "description": "Number of frames dropped on this channel since the last delivered frame (slow-consumer signal). Absent when zero."
          },
          "payload": {
            "$ref": "#/components/schemas/PriceCryptoPayload"
          }
        },
        "description": "Snapshot payload is a symbol batch; live payload is a single point."
      },
      "PriceCryptoPayload": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/CryptoPoint"
          },
          {
            "type": "object",
            "required": [
              "symbol",
              "source",
              "data"
            ],
            "properties": {
              "symbol": {
                "type": "string"
              },
              "source": {
                "type": "string",
                "enum": [
                  "pyth",
                  "chainlink"
                ],
                "description": "Vendor that produced every point in this batch, once at batch level (points are source-free): symbol ownership is fixed for the life of a pod, so one series never mixes vendors. After a reconnect during an ownership flip, the batch can carry a different source than the previous connection's frames."
              },
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/BatchPoint"
                },
                "description": "The array holds the previous two minutes of points by event time and is empty for a symbol with no recent data."
              }
            }
          }
        ]
      },
      "PriceEquityEnvelope": {
        "type": "object",
        "required": [
          "v",
          "channel",
          "seq",
          "ts",
          "payload"
        ],
        "properties": {
          "v": {
            "type": "integer",
            "const": 1,
            "description": "Envelope version."
          },
          "channel": {
            "type": "string",
            "const": "price.equity"
          },
          "seq": {
            "type": "integer",
            "description": "Dense per-connection, per-channel sequence, assigned at delivery. Resets on every reconnect. Snapshot frames consume a seq."
          },
          "ts": {
            "type": "integer",
            "description": "Producer event time, Unix milliseconds."
          },
          "snapshot": {
            "type": "boolean",
            "description": "Present and true on the one snapshot frame sent after each new subscription. Absent on live frames."
          },
          "dropped": {
            "type": "integer",
            "description": "Number of frames dropped on this channel since the last delivered frame (slow-consumer signal). Absent when zero."
          },
          "payload": {
            "$ref": "#/components/schemas/PriceEquityPayload"
          }
        },
        "description": "Snapshot payload is a symbol batch; live payload is a single point."
      },
      "PriceEquityPayload": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/EquityPoint"
          },
          {
            "type": "object",
            "required": [
              "symbol",
              "source",
              "data"
            ],
            "properties": {
              "symbol": {
                "type": "string"
              },
              "source": {
                "type": "string",
                "enum": [
                  "pyth",
                  "massive",
                  "chainlink"
                ],
                "description": "Vendor that produced every point in this batch, once at batch level: symbol ownership is fixed for the life of a pod, so one series never mixes vendors. After a reconnect during an ownership flip, the batch can carry a different source than the previous connection's frames."
              },
              "market_status": {
                "type": "string",
                "enum": [
                  "open",
                  "closed",
                  "unknown"
                ],
                "description": "The series' current market status, once at batch level like source; absent when not stated. While it is closed or unknown, data also carries the series' last price point when that point is older than the two-minute window (the price the market stopped at), and the envelope ts is then that point's time."
              },
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/BatchPoint"
                },
                "description": "The array holds the previous two minutes of points by event time and is empty for a symbol with no recent data, except while market_status is closed or unknown: then it also holds the series' last point when that point is older than the window."
              }
            }
          }
        ]
      },
      "PriceCryptoTwapEnvelope": {
        "type": "object",
        "required": [
          "v",
          "channel",
          "seq",
          "ts",
          "payload"
        ],
        "properties": {
          "v": {
            "type": "integer",
            "const": 1,
            "description": "Envelope version."
          },
          "channel": {
            "type": "string",
            "const": "price.crypto.twap"
          },
          "seq": {
            "type": "integer",
            "description": "Dense per-connection, per-channel sequence, assigned at delivery. Resets on every reconnect. Snapshot frames consume a seq."
          },
          "ts": {
            "type": "integer",
            "description": "Producer event time, Unix milliseconds."
          },
          "snapshot": {
            "type": "boolean",
            "description": "Present and true on the one snapshot frame sent after each new subscription. Absent on live frames."
          },
          "dropped": {
            "type": "integer",
            "description": "Number of frames dropped on this channel since the last delivered frame (slow-consumer signal). Absent when zero."
          },
          "payload": {
            "$ref": "#/components/schemas/PriceCryptoTwapPayload"
          }
        },
        "description": "Snapshot payload is a symbol batch carrying window_seconds; live payload is a single point carrying window_seconds."
      },
      "PriceCryptoTwapPayload": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/TwapPoint"
          },
          {
            "type": "object",
            "required": [
              "symbol",
              "data",
              "window_seconds",
              "source"
            ],
            "properties": {
              "symbol": {
                "type": "string"
              },
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/BatchPoint"
                },
                "description": "The array holds the previous two minutes of points by event time and is empty for a symbol with no recent data."
              },
              "window_seconds": {
                "type": "integer",
                "const": 60
              },
              "source": {
                "type": "string",
                "enum": [
                  "chainlink"
                ],
                "description": "The producing vendor, once per batch (points are source-free). Topic constant on this single-provider lane."
              }
            }
          }
        ]
      },
      "PriceEquityTwapEnvelope": {
        "type": "object",
        "required": [
          "v",
          "channel",
          "seq",
          "ts",
          "payload"
        ],
        "properties": {
          "v": {
            "type": "integer",
            "const": 1,
            "description": "Envelope version."
          },
          "channel": {
            "type": "string",
            "const": "price.equity.twap"
          },
          "seq": {
            "type": "integer",
            "description": "Dense per-connection, per-channel sequence, assigned at delivery. Resets on every reconnect. Snapshot frames consume a seq."
          },
          "ts": {
            "type": "integer",
            "description": "Producer event time, Unix milliseconds."
          },
          "snapshot": {
            "type": "boolean",
            "description": "Present and true on the one snapshot frame sent after each new subscription. Absent on live frames."
          },
          "dropped": {
            "type": "integer",
            "description": "Number of frames dropped on this channel since the last delivered frame (slow-consumer signal). Absent when zero."
          },
          "payload": {
            "$ref": "#/components/schemas/PriceEquityTwapPayload"
          }
        },
        "description": "Snapshot payload is a symbol batch carrying window_seconds; live payload is a single point carrying window_seconds."
      },
      "PriceEquityTwapPayload": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/EquityTwapPoint"
          },
          {
            "type": "object",
            "required": [
              "symbol",
              "source",
              "data",
              "window_seconds"
            ],
            "properties": {
              "symbol": {
                "type": "string"
              },
              "source": {
                "type": "string",
                "enum": [
                  "pyth",
                  "massive",
                  "chainlink"
                ],
                "description": "Vendor that produced every point in this batch, once at batch level. The channel is Chainlink-only today, so the value is chainlink."
              },
              "market_status": {
                "type": "string",
                "enum": [
                  "open",
                  "closed",
                  "unknown"
                ],
                "description": "The series' current market status, once at batch level like source; absent when not stated. While it is closed or unknown, data also carries the series' last price point when that point is older than the two-minute window (the price the market stopped at), and the envelope ts is then that point's time."
              },
              "data": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/BatchPoint"
                },
                "description": "The array holds the previous two minutes of points by event time and is empty for a symbol with no recent data, except while market_status is closed or unknown: then it also holds the series' last point when that point is older than the window."
              },
              "window_seconds": {
                "type": "integer",
                "const": 60
              }
            }
          }
        ]
      },
      "ChallengeAck": {
        "type": "object",
        "required": [
          "op",
          "nonce"
        ],
        "properties": {
          "op": {
            "const": "challenge"
          },
          "nonce": {
            "type": "string",
            "pattern": "^[A-Za-z0-9_-]{1,255}$",
            "description": "This connection's challenge nonce (a UUID). Render the Turnstile widget with it as cData before minting a ticket at POST /api/ws-ticket; the ticket then verifies only on this socket, whatever IP either request came from. Not a secret, never rotated for the life of the connection."
          },
          "rid": {
            "type": "string"
          }
        }
      },
      "ChallengeRequest": {
        "type": "object",
        "required": [
          "op"
        ],
        "properties": {
          "op": {
            "const": "challenge"
          },
          "rid": {
            "type": "string"
          }
        }
      }
    }
  }
}
