{
  "asyncapi": "3.0.0",
  "id": "urn:coinpost:oracle:websocket",
  "info": {
    "title": "Oracle WebSocket API",
    "version": "2.0.0",
    "description": "Oracle server WebSocket contract. Operations use the server perspective: receive means client to Oracle; send means Oracle to client. Prices and volumes are decimal strings; timestamps are Unix milliseconds.",
    "externalDocs": {
      "url": "https://developers.coinpost.ai/api/oracle/websocket"
    }
  },
  "defaultContentType": "application/json",
  "servers": {
    "production": {
      "host": "api.coinpost.ai",
      "protocol": "wss",
      "description": "When CP Auth is enabled, send `Authorization: Bearer <token>` with the `oracle` scope in the upgrade request. Browser clients may use `?token=<token>` or `?access_token=<token>` on this endpoint.\n\nMarket subscriptions stream new events; they do not send an initial market snapshot. Fetch initial data through the REST API if needed. After reconnecting, send your subscriptions again. Market events do not include a subscription ID; overlapping subscriptions receive each matching event once.",
      "security": [
        {
          "$ref": "#/components/securitySchemes/bearer"
        },
        {
          "$ref": "#/components/securitySchemes/token"
        },
        {
          "$ref": "#/components/securitySchemes/accessToken"
        }
      ]
    }
  },
  "channels": {
    "oracle": {
      "address": "/oracle/v2/ws",
      "title": "Oracle WebSocket connection",
      "servers": [
        {
          "$ref": "#/servers/production"
        }
      ],
      "bindings": {
        "ws": {
          "method": "GET",
          "bindingVersion": "0.1.0",
          "query": {
            "type": "object",
            "properties": {
              "token": {
                "type": "string"
              },
              "access_token": {
                "type": "string"
              }
            }
          },
          "headers": {
            "type": "object",
            "properties": {
              "Authorization": {
                "type": "string",
                "description": "Bearer access token with oracle scope, when CP Auth is enabled."
              }
            }
          }
        }
      },
      "messages": {
        "subscribe": {
          "$ref": "#/components/messages/subscribe"
        },
        "subscribed": {
          "$ref": "#/components/messages/subscribed"
        },
        "unsubscribe": {
          "$ref": "#/components/messages/unsubscribe"
        },
        "unsubscribed": {
          "$ref": "#/components/messages/unsubscribed"
        },
        "ping": {
          "$ref": "#/components/messages/ping"
        },
        "pong": {
          "$ref": "#/components/messages/pong"
        },
        "kline": {
          "$ref": "#/components/messages/kline"
        },
        "ticker": {
          "$ref": "#/components/messages/ticker"
        },
        "sourceTicker": {
          "$ref": "#/components/messages/sourceTicker"
        },
        "status": {
          "$ref": "#/components/messages/status"
        },
        "error": {
          "$ref": "#/components/messages/error"
        }
      }
    }
  },
  "operations": {
    "subscribe": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/oracle"
      },
      "messages": [
        {
          "$ref": "#/channels/oracle/messages/subscribe"
        }
      ],
      "reply": {
        "channel": {
          "$ref": "#/channels/oracle"
        },
        "messages": [
          {
            "$ref": "#/channels/oracle/messages/subscribed"
          }
        ]
      }
    },
    "subscribed": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/oracle"
      },
      "messages": [
        {
          "$ref": "#/channels/oracle/messages/subscribed"
        }
      ]
    },
    "unsubscribe": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/oracle"
      },
      "messages": [
        {
          "$ref": "#/channels/oracle/messages/unsubscribe"
        }
      ],
      "reply": {
        "channel": {
          "$ref": "#/channels/oracle"
        },
        "messages": [
          {
            "$ref": "#/channels/oracle/messages/unsubscribed"
          }
        ]
      }
    },
    "unsubscribed": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/oracle"
      },
      "messages": [
        {
          "$ref": "#/channels/oracle/messages/unsubscribed"
        }
      ]
    },
    "ping": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/oracle"
      },
      "messages": [
        {
          "$ref": "#/channels/oracle/messages/ping"
        }
      ],
      "reply": {
        "channel": {
          "$ref": "#/channels/oracle"
        },
        "messages": [
          {
            "$ref": "#/channels/oracle/messages/pong"
          }
        ]
      }
    },
    "pong": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/oracle"
      },
      "messages": [
        {
          "$ref": "#/channels/oracle/messages/pong"
        }
      ]
    },
    "kline": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/oracle"
      },
      "messages": [
        {
          "$ref": "#/channels/oracle/messages/kline"
        }
      ]
    },
    "ticker": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/oracle"
      },
      "messages": [
        {
          "$ref": "#/channels/oracle/messages/ticker"
        }
      ]
    },
    "sourceTicker": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/oracle"
      },
      "messages": [
        {
          "$ref": "#/channels/oracle/messages/sourceTicker"
        }
      ]
    },
    "status": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/oracle"
      },
      "messages": [
        {
          "$ref": "#/channels/oracle/messages/status"
        }
      ]
    },
    "error": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/oracle"
      },
      "messages": [
        {
          "$ref": "#/channels/oracle/messages/error"
        }
      ]
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "When CP Auth is enabled, supply a token with oracle scope. Use either this header or a query token."
      },
      "token": {
        "type": "httpApiKey",
        "in": "query",
        "name": "token",
        "description": "Browser alternative to the Authorization header when CP Auth is enabled."
      },
      "accessToken": {
        "type": "httpApiKey",
        "in": "query",
        "name": "access_token",
        "description": "Alias for the token query parameter."
      }
    },
    "schemas": {
      "Subscribe": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "description": "Always `subscribe`.",
            "const": "subscribe"
          },
          "id": {
            "type": "string",
            "description": "Subscription ID. Default: `default`. Reuse it to replace the subscription.",
            "default": "default"
          },
          "symbol": {
            "type": "string",
            "description": "Base asset, e.g. `BTC`; do not append the quote currency."
          },
          "symbols": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Base assets. Combined with `symbol` when both are supplied."
          },
          "quote": {
            "type": "string",
            "description": "`JPY` (default) or `USD`.",
            "enum": [
              "JPY",
              "USD"
            ],
            "default": "JPY"
          },
          "channels": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "klines",
                "tickers",
                "sourceTickers",
                "status"
              ]
            },
            "description": "`klines`, `tickers`, `sourceTickers`, `status`. Default: the first three.",
            "default": [
              "klines",
              "tickers",
              "sourceTickers"
            ]
          },
          "intervals": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "1m",
                "5m",
                "15m",
                "30m",
                "1h",
                "1d",
                "1w",
                "1M"
              ]
            },
            "description": "Klines only: `1m`, `5m`, `15m`, `30m`, `1h`, `1d`, `1w`, `1M`. Omit for all intervals."
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Source tickers only. Exchange IDs such as `binance`, `bitbank`. Omit for all sources."
          }
        },
        "required": [
          "type"
        ]
      },
      "Unsubscribe": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "description": "Always `unsubscribe`.",
            "const": "unsubscribe"
          },
          "id": {
            "type": "string",
            "description": "Subscription to remove. Default: `default`.",
            "default": "default"
          }
        },
        "required": [
          "type"
        ]
      },
      "Ping": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "description": "Always `ping`.",
            "const": "ping"
          },
          "ts": {
            "type": "string",
            "description": "Opaque value echoed in the pong; defaults to an empty string.",
            "default": ""
          }
        },
        "required": [
          "type"
        ]
      },
      "Kline": {
        "type": "object",
        "properties": {
          "e": {
            "type": "string",
            "description": "Always `kline`.",
            "const": "kline"
          },
          "E": {
            "type": "integer",
            "description": "Oracle publish time, Unix milliseconds."
          },
          "s": {
            "type": "string",
            "description": "Base asset symbol."
          },
          "quote": {
            "type": "string",
            "description": "Output price currency: `JPY` or `USD`.",
            "enum": [
              "JPY",
              "USD"
            ]
          },
          "i": {
            "type": "string",
            "description": "Kline interval.",
            "enum": [
              "1m",
              "5m",
              "15m",
              "30m",
              "1h",
              "1d",
              "1w",
              "1M"
            ]
          },
          "x": {
            "type": "boolean",
            "description": "Whether the Kline is closed."
          },
          "k": {
            "type": "array",
            "description": "`[open_time_ms, open, high, low, close, volume, is_closed]`. Time is an integer; prices and volume are decimal strings; the last value is a boolean.",
            "minItems": 7,
            "maxItems": 7,
            "items": [
              {
                "type": "integer",
                "description": "Open time in Unix milliseconds."
              },
              {
                "type": "string",
                "pattern": "^-?[0-9]+(?:\\.[0-9]+)?$",
                "description": "Open price as a decimal string."
              },
              {
                "type": "string",
                "pattern": "^-?[0-9]+(?:\\.[0-9]+)?$",
                "description": "High price as a decimal string."
              },
              {
                "type": "string",
                "pattern": "^-?[0-9]+(?:\\.[0-9]+)?$",
                "description": "Low price as a decimal string."
              },
              {
                "type": "string",
                "pattern": "^-?[0-9]+(?:\\.[0-9]+)?$",
                "description": "Close price as a decimal string."
              },
              {
                "type": "string",
                "pattern": "^-?[0-9]+(?:\\.[0-9]+)?$",
                "description": "Base-asset volume as a decimal string."
              },
              {
                "type": "boolean",
                "description": "Whether the candle is closed."
              }
            ],
            "additionalItems": false
          }
        },
        "required": [
          "e",
          "E",
          "s",
          "quote",
          "i",
          "x",
          "k"
        ]
      },
      "Ticker": {
        "type": "object",
        "properties": {
          "e": {
            "type": "string",
            "description": "Always `ticker`.",
            "const": "ticker"
          },
          "E": {
            "type": "integer",
            "description": "Oracle publish time, Unix milliseconds."
          },
          "s": {
            "type": "string",
            "description": "Base asset symbol."
          },
          "quote": {
            "type": "string",
            "description": "Output price currency: `JPY` or `USD`.",
            "enum": [
              "JPY",
              "USD"
            ]
          },
          "c": {
            "type": "string",
            "description": "Latest price, as a decimal string.",
            "pattern": "^-?[0-9]+(?:\\.[0-9]+)?$"
          },
          "v": {
            "type": "string",
            "description": "24-hour base-asset volume, as a decimal string.",
            "pattern": "^-?[0-9]+(?:\\.[0-9]+)?$"
          },
          "p": {
            "type": "string",
            "description": "24-hour absolute price change in `quote`.",
            "pattern": "^-?[0-9]+(?:\\.[0-9]+)?$"
          },
          "P": {
            "type": "string",
            "description": "24-hour price change in percent; `2.5` means 2.5%.",
            "pattern": "^-?[0-9]+(?:\\.[0-9]+)?$"
          },
          "P1h": {
            "type": "string",
            "description": "1-hour price change in percent.",
            "pattern": "^-?[0-9]+(?:\\.[0-9]+)?$"
          },
          "P7d": {
            "type": "string",
            "description": "7-day price change in percent.",
            "pattern": "^-?[0-9]+(?:\\.[0-9]+)?$"
          },
          "P30d": {
            "type": "string",
            "description": "30-day price change in percent.",
            "pattern": "^-?[0-9]+(?:\\.[0-9]+)?$"
          }
        },
        "required": [
          "e",
          "E",
          "s",
          "quote",
          "c"
        ]
      },
      "SourceTicker": {
        "type": "object",
        "properties": {
          "e": {
            "type": "string",
            "description": "Always `sourceTicker`.",
            "const": "sourceTicker"
          },
          "E": {
            "type": "integer",
            "description": "Oracle publish time, Unix milliseconds."
          },
          "s": {
            "type": "string",
            "description": "Base asset symbol."
          },
          "quote": {
            "type": "string",
            "description": "Output price currency: `JPY` or `USD`.",
            "enum": [
              "JPY",
              "USD"
            ]
          },
          "T": {
            "type": "integer",
            "description": "Upstream quote time, Unix milliseconds."
          },
          "x": {
            "type": "string",
            "description": "Exchange ID."
          },
          "p": {
            "type": "string",
            "description": "Native exchange pair, e.g. `BTCUSDT`."
          },
          "q": {
            "type": "string",
            "description": "Native quote currency, e.g. `USDT`."
          },
          "c": {
            "type": "string",
            "description": "Latest price, as a decimal string.",
            "pattern": "^-?[0-9]+(?:\\.[0-9]+)?$"
          },
          "b": {
            "type": "string",
            "description": "Best bid price in `quote`.",
            "pattern": "^-?[0-9]+(?:\\.[0-9]+)?$"
          },
          "a": {
            "type": "string",
            "description": "Best ask price in `quote`.",
            "pattern": "^-?[0-9]+(?:\\.[0-9]+)?$"
          },
          "o": {
            "type": "string",
            "description": "24-hour open price in `quote`.",
            "pattern": "^-?[0-9]+(?:\\.[0-9]+)?$"
          },
          "h": {
            "type": "string",
            "description": "24-hour high price in `quote`.",
            "pattern": "^-?[0-9]+(?:\\.[0-9]+)?$"
          },
          "l": {
            "type": "string",
            "description": "24-hour low price in `quote`.",
            "pattern": "^-?[0-9]+(?:\\.[0-9]+)?$"
          },
          "v": {
            "type": "string",
            "description": "24-hour base-asset volume, as a decimal string.",
            "pattern": "^-?[0-9]+(?:\\.[0-9]+)?$"
          },
          "vc": {
            "type": "boolean",
            "description": "Whether the reported volume has complete coverage."
          }
        },
        "required": [
          "e",
          "E",
          "s",
          "quote",
          "T",
          "x",
          "p",
          "q",
          "c"
        ]
      },
      "Status": {
        "type": "object",
        "properties": {
          "e": {
            "type": "string",
            "description": "Always `status`.",
            "const": "status"
          },
          "E": {
            "type": "integer",
            "description": "Oracle publish time, Unix milliseconds."
          },
          "status": {
            "type": "string",
            "description": "`operational`, `planned_maintenance`, `partial_outage`, `full_outage`.",
            "enum": [
              "operational",
              "planned_maintenance",
              "partial_outage",
              "full_outage"
            ]
          },
          "severity": {
            "type": "string",
            "description": "`info`, `warning`, or `critical`.",
            "enum": [
              "info",
              "warning",
              "critical"
            ]
          },
          "scope": {
            "type": "object",
            "description": "Impact scope: `system` (boolean), `assets` (string array), `sources` (string array). System-wide scope does not name assets or sources.",
            "properties": {
              "system": {
                "type": "boolean"
              },
              "assets": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "sources": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "occurredAt": {
            "type": "integer",
            "description": "Status occurrence time, Unix milliseconds."
          },
          "scheduledEndAt": {
            "type": "integer",
            "description": "Expected maintenance end in Unix milliseconds. Required for planned_maintenance; optional for other states."
          },
          "message": {
            "type": "string",
            "description": "Additional status details."
          },
          "sourceStates": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SourceState"
            },
            "description": "Complete snapshot of every source state, sorted by `id`. Sent on subscription, reconnect and resynchronization, when `sourceView.kind` is `snapshot`."
          },
          "sourceChange": {
            "$ref": "#/components/schemas/SourceStateChange",
            "description": "One source transition, when `sourceView.kind` is `change`. A pair change or recovery is sent even when the overall service status is unchanged."
          },
          "sourceView": {
            "$ref": "#/components/schemas/SourceView",
            "description": "Provenance of `sourceStates` or `sourceChange`; omitted on an operational transition that carries no source view."
          }
        },
        "required": [
          "e",
          "E",
          "status",
          "severity",
          "scope",
          "occurredAt"
        ],
        "if": {
          "properties": {
            "status": {
              "const": "planned_maintenance"
            }
          }
        },
        "then": {
          "required": [
            "scheduledEndAt"
          ]
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "description": "Always `error`.",
            "const": "error"
          },
          "code": {
            "type": "string",
            "description": "Machine-readable error code; see the error codes above.",
            "enum": [
              "invalid_subscription",
              "subscription_limit",
              "symbol_limit",
              "unknown_message",
              "subscribe_failed"
            ]
          },
          "message": {
            "type": "string",
            "description": "Human-readable details."
          }
        },
        "required": [
          "type",
          "code",
          "message"
        ]
      },
      "Subscribed": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "const": "subscribed"
          },
          "subscription_id": {
            "type": "string",
            "description": "The accepted request id, or `default` if the request id was omitted or blank."
          }
        },
        "required": [
          "type",
          "subscription_id"
        ]
      },
      "Unsubscribed": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "const": "unsubscribed"
          },
          "subscription_id": {
            "type": "string",
            "description": "The accepted request id, or `default` if the request id was omitted or blank."
          }
        },
        "required": [
          "type",
          "subscription_id"
        ]
      },
      "Pong": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "const": "pong"
          },
          "ts": {
            "type": "string"
          }
        },
        "required": [
          "type",
          "ts"
        ]
      },
      "SourceScope": {
        "type": "object",
        "description": "Identity of one source scope: an exchange capability, optionally narrowed to a pair and interval.",
        "required": [
          "exchange",
          "capability"
        ],
        "properties": {
          "exchange": {
            "type": "string",
            "description": "Exchange ID, e.g. `binance`."
          },
          "capability": {
            "type": "string",
            "enum": [
              "realtime_ws",
              "history_rest"
            ],
            "description": "Realtime WebSocket feed or history REST capability."
          },
          "pair": {
            "type": "string",
            "description": "Native exchange market; omitted for an exchange-wide scope."
          },
          "interval": {
            "type": "string",
            "description": "Native interval (`1m`, `1h`, `1d`); omitted for an exchange- or pair-wide scope."
          }
        }
      },
      "SourceState": {
        "type": "object",
        "description": "One shared source state. REST `GET /v2/source-status` exposes the same states as `source_states` with snake_case names and RFC 3339 timestamps.",
        "required": [
          "id",
          "scope",
          "state",
          "freshness",
          "observedAt",
          "changedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable scope identifier (`exchange/capability[/pair[/interval]]`)."
          },
          "scope": {
            "$ref": "#/components/schemas/SourceScope"
          },
          "state": {
            "type": "string",
            "description": "Realtime scopes use `starting`, `ok`, `reconnecting`, `reconfiguring`, `stale`, `maintenance`; history scopes use `healthy`, `observing`, `isolated`, `recovering`; `unknown` when no fresh report is available; `disabled` when the operator disabled the scope; `removed` when a realtime scope left the authoritative inventory."
          },
          "freshness": {
            "type": "string",
            "enum": [
              "fresh",
              "stale",
              "unknown"
            ],
            "description": "Whether the worker report backing this state is current."
          },
          "reason": {
            "type": "string",
            "enum": [
              "maintenance",
              "upstream_unavailable",
              "transport",
              "request_timeout",
              "message_stale",
              "report_stale",
              "report_missing",
              "unverified",
              "stream_gap"
            ],
            "description": "Why the state is not healthy, when applicable."
          },
          "lastKnownState": {
            "type": "string",
            "description": "The last state seen while the report was fresh, retained while freshness is stale or unknown."
          },
          "lastKnownReason": {
            "type": "string",
            "description": "The reason recorded with `lastKnownState`."
          },
          "base": {
            "type": "string",
            "description": "Base asset of a pair scope."
          },
          "quote": {
            "type": "string",
            "description": "Native quote currency of a pair scope."
          },
          "enabled": {
            "type": "boolean",
            "description": "Operator enablement of the scope; automatic isolation never changes it."
          },
          "observedAt": {
            "type": "integer",
            "description": "When this state was observed, Unix milliseconds."
          },
          "changedAt": {
            "type": "integer",
            "description": "When the state last changed, Unix milliseconds."
          },
          "lastMessageAt": {
            "type": "integer",
            "description": "Realtime scopes: last upstream message, Unix milliseconds."
          },
          "lastTradeAt": {
            "type": "integer",
            "description": "Realtime scopes: last upstream trade, Unix milliseconds."
          },
          "lastSuccess": {
            "type": "integer",
            "description": "History scopes: last validated history success, Unix milliseconds."
          },
          "nextProbe": {
            "type": "integer",
            "description": "History scopes: next scheduled recovery probe, Unix milliseconds."
          }
        }
      },
      "SourceStateChange": {
        "type": "object",
        "description": "One source transition.",
        "required": [
          "current",
          "recovery"
        ],
        "properties": {
          "current": {
            "$ref": "#/components/schemas/SourceState"
          },
          "previousState": {
            "type": "string",
            "description": "The state before this transition; omitted for a newly observed source."
          },
          "recovery": {
            "type": "boolean",
            "description": "True when a previously faulty source has been validated healthy again."
          }
        }
      },
      "SourceView": {
        "type": "object",
        "description": "Provenance of the source view. Clients replace their view on a `snapshot` and apply a `change` only when its revision is newer within the same publisher and epoch; a revision gap or a changed epoch requires resynchronization, which the server answers with a fresh snapshot.",
        "required": [
          "schemaVersion",
          "publisherId",
          "epoch",
          "revision",
          "kind",
          "complete",
          "observedAt"
        ],
        "properties": {
          "schemaVersion": {
            "type": "integer",
            "description": "Always `1`."
          },
          "publisherId": {
            "type": "string",
            "description": "The API publisher that produced the view."
          },
          "epoch": {
            "type": "string",
            "description": "Publisher epoch; changes when the publisher restarts its view."
          },
          "revision": {
            "type": "integer",
            "description": "Monotonic within a publisher epoch."
          },
          "kind": {
            "type": "string",
            "enum": [
              "snapshot",
              "change"
            ],
            "description": "`snapshot` carries `sourceStates`; `change` carries `sourceChange`."
          },
          "complete": {
            "type": "boolean",
            "description": "True when a snapshot lists every known source."
          },
          "observedAt": {
            "type": "integer",
            "description": "When the view was produced, Unix milliseconds."
          },
          "resync": {
            "type": "boolean",
            "description": "Set when the publisher detected a revision gap and republished a full snapshot."
          }
        }
      }
    },
    "messages": {
      "subscribe": {
        "name": "subscribe",
        "title": "Subscribe",
        "description": "Use `type: \"subscribe\"` to create or replace a subscription. Choose the data feed with `channels`: `klines`, `tickers`, `sourceTickers`, or `status`. Each subscription reference page describes one feed and lists only its applicable fields. To select only that feed, explicitly provide the shown `channels` value.\n\n### Subscription limits {#subscription-limits}\n\nA connection supports up to 100 subscriptions and 100 unique symbols.\n\nA market subscription that omits both `symbol` and `symbols` uses the connection's entire symbol budget. A connection can have only one such subscription, and it cannot coexist with subscriptions naming individual symbols or another all-asset subscription, even for a different `quote`. Additional market subscriptions require a separate connection. A subscription containing only the `status` channel and no symbols can coexist with it. Violating this limit returns an error with `code: \"symbol_limit\"`; the existing subscriptions stay active. Reusing the same subscription `id` replaces that subscription.",
        "payload": {
          "$ref": "#/components/schemas/Subscribe"
        },
        "examples": [
          {
            "name": "klines",
            "payload": {
              "type": "subscribe",
              "id": "btc-klines",
              "symbol": "BTC",
              "quote": "JPY",
              "channels": [
                "klines"
              ],
              "intervals": [
                "1m",
                "5m"
              ]
            }
          },
          {
            "name": "tickers",
            "payload": {
              "type": "subscribe",
              "id": "btc-tickers",
              "symbol": "BTC",
              "quote": "JPY",
              "channels": [
                "tickers"
              ]
            }
          },
          {
            "name": "tickers-all",
            "payload": {
              "type": "subscribe",
              "id": "all-usd",
              "quote": "USD",
              "channels": [
                "tickers"
              ]
            }
          },
          {
            "name": "source-tickers",
            "payload": {
              "type": "subscribe",
              "id": "btc-sources",
              "symbol": "BTC",
              "quote": "JPY",
              "channels": [
                "sourceTickers"
              ],
              "sources": [
                "binance",
                "bitbank"
              ]
            }
          },
          {
            "name": "status",
            "payload": {
              "type": "subscribe",
              "id": "service-status",
              "channels": [
                "status"
              ]
            }
          },
          {
            "name": "combined",
            "payload": {
              "type": "subscribe",
              "id": "btc-market",
              "symbol": "BTC",
              "quote": "JPY",
              "channels": [
                "klines",
                "tickers",
                "sourceTickers"
              ],
              "intervals": [
                "1m"
              ],
              "sources": [
                "binance"
              ]
            }
          }
        ],
        "x-docs-profiles": [
          {
            "channel": "klines",
            "fields": [
              "type",
              "id",
              "symbol",
              "symbols",
              "quote",
              "channels",
              "intervals"
            ],
            "examples": [
              "klines"
            ],
            "title": "Kline subscription",
            "description": "Receive `kline` events for the selected assets, quote, and intervals. Omit `intervals` for all supported intervals. Omit both `symbol` and `symbols` for all assets in this quote, subject to the connection-wide symbol limit."
          },
          {
            "channel": "tickers",
            "fields": [
              "type",
              "id",
              "symbol",
              "symbols",
              "quote",
              "channels"
            ],
            "examples": [
              "tickers",
              "tickers-all"
            ],
            "title": "Aggregate ticker subscription",
            "description": "Receive `ticker` events for the selected assets and quote. This feed has no interval or source filter. Omit both asset fields for all assets in the selected quote; that subscription consumes the full symbol budget."
          },
          {
            "channel": "sourceTickers",
            "fields": [
              "type",
              "id",
              "symbol",
              "symbols",
              "quote",
              "channels",
              "sources"
            ],
            "examples": [
              "source-tickers"
            ],
            "title": "Source ticker subscription",
            "description": "Receive `sourceTicker` events. `sources` filters exchange IDs; omit it for all sources. Prices use `quote`, while the event retains the native pair and native quote. No interval filter applies."
          },
          {
            "channel": "status",
            "fields": [
              "type",
              "id",
              "channels"
            ],
            "examples": [
              "status"
            ],
            "title": "Service status subscription",
            "description": "Receive `status` events: the current state when available and subsequent transitions. A status-only subscription needs no asset, quote, interval, or source parameters. With no asset fields, it can coexist with an all-asset market subscription."
          }
        ],
        "x-docs-combined": {
          "title": "Combined subscriptions and defaults",
          "description": "To receive multiple feeds with one ID, list them together in `channels`. `intervals` applies only to `klines`; `sources` applies only to `sourceTickers`. If `channels` is omitted or empty, the defaults are `klines`, `tickers`, and `sourceTickers`; `status` must be added explicitly. Reusing an ID replaces its full subscription, so send all desired fields again. Distinct subscriptions on one connection need distinct IDs; omitting `id` always uses `default`.",
          "examples": [
            "combined"
          ]
        }
      },
      "unsubscribe": {
        "name": "unsubscribe",
        "title": "Unsubscribe",
        "description": "Remove the entire subscription identified by the request `id`, including all its channels. Omit or leave `id` blank to remove `default`. The server confirms using `subscription_id`, including when the ID does not exist.",
        "payload": {
          "$ref": "#/components/schemas/Unsubscribe"
        },
        "examples": [
          {
            "name": "unsubscribe-1",
            "payload": {
              "type": "unsubscribe",
              "id": "btc-jpy"
            }
          }
        ]
      },
      "ping": {
        "name": "ping",
        "title": "Application heartbeat",
        "description": "The application heartbeat echoes `ts` in a `pong` message. It is separate from WebSocket protocol ping/pong frames. The server sends protocol pings every 30 seconds and requires a protocol pong within the 90-second read deadline; browsers handle those frames automatically.",
        "payload": {
          "$ref": "#/components/schemas/Ping"
        },
        "examples": [
          {
            "name": "ping-1",
            "payload": {
              "type": "ping",
              "ts": "1787974923000"
            }
          }
        ]
      },
      "kline": {
        "name": "kline",
        "title": "Kline event",
        "description": "Kline: `k` uses the REST order `[open_time_ms, open, high, low, close, volume, is_closed]`. `x=false` means the candle is still forming. After closed history is stored, the same candle is sent again with `x=true` and its final OHLCV; replace the active row with this final row. `s` is the base asset and `quote` is the price currency.",
        "payload": {
          "$ref": "#/components/schemas/Kline"
        },
        "examples": [
          {
            "name": "kline-1",
            "payload": {
              "e": "kline",
              "E": 1787974923000,
              "s": "BTC",
              "quote": "JPY",
              "i": "1m",
              "x": false,
              "k": [
                1787974920000,
                "15000000",
                "15010000",
                "14990000",
                "15005000",
                "12.5",
                false
              ]
            }
          }
        ]
      },
      "ticker": {
        "name": "ticker",
        "title": "Aggregate ticker event",
        "description": "Aggregate ticker: `c` is the latest price and `v` is rolling 24-hour base-asset volume. Optional `p`, `P`, `P1h`, `P7d`, and `P30d` provide price changes. `v` is omitted until a complete 24-hour minute history exists. Minute-derived volume adds the current active minute to the preceding 1,440 closed minutes, so it can include up to one extra partial minute compared with a trade-by-trade window.",
        "payload": {
          "$ref": "#/components/schemas/Ticker"
        },
        "examples": [
          {
            "name": "ticker-1",
            "payload": {
              "e": "ticker",
              "E": 1787974923000,
              "s": "BTC",
              "quote": "JPY",
              "c": "15005000",
              "v": "123.45",
              "P": "2.5"
            }
          }
        ]
      },
      "sourceTicker": {
        "name": "sourceTicker",
        "title": "Source ticker event",
        "description": "Source ticker: `s` is the base asset and `quote` is the normalized output currency; `p` and `q` preserve the native pair and quote. `c`, `b`, `a`, `o`, `h`, and `l` use the currency in `quote`; `v` is base-asset quantity. Bid/ask or derived 24-hour fields may be absent while upstream quotes, FX, or usable recent minute data are unavailable. `T` is source time; `E` is Oracle publish time.\n\n`vc` is true for native rolling-volume feeds or when all 1,440 preceding source minutes are present; false means `v` sums available bars and may be understated. Minute-derived `v` also includes the current active minute, so it can span up to one extra partial minute. Cross-currency live prices use the latest available FX rate at publication; `T` remains the upstream quote time.",
        "payload": {
          "$ref": "#/components/schemas/SourceTicker"
        },
        "examples": [
          {
            "name": "sourceTicker-1",
            "payload": {
              "e": "sourceTicker",
              "E": 1788220801000,
              "T": 1788220800123,
              "s": "BTC",
              "quote": "JPY",
              "x": "binance",
              "p": "BTCUSDT",
              "q": "USDT",
              "c": "15018000",
              "b": "15017000",
              "a": "15019000",
              "v": "1234.5678",
              "vc": true
            }
          }
        ]
      },
      "status": {
        "name": "status",
        "title": "Operational status event",
        "description": "Subscribe to `status` to receive the current state when available, followed by later transitions. Planned maintenance includes `scheduledEndAt`; other states may omit it. Status events ignore symbol, quote, interval, and source filters. Monitor [GET /v2/_hc](https://developers.coinpost.ai/api/oracle/operations/gethealthcheck) independently because disconnected clients cannot receive outage messages. Source availability uses the same channel and envelope: `sourceStates` is a complete snapshot on subscription, reconnect and resynchronization, `sourceChange` is one source transition, and `sourceView` says how to apply them. Source identity distinguishes `realtime_ws` exchange/pair feeds from `history_rest` pair/interval capabilities, including auxiliary providers.",
        "payload": {
          "$ref": "#/components/schemas/Status"
        },
        "examples": [
          {
            "name": "status-1",
            "payload": {
              "e": "status",
              "E": 1788220801000,
              "status": "partial_outage",
              "severity": "warning",
              "scope": {
                "system": true
              },
              "occurredAt": 1788220801000
            }
          },
          {
            "name": "status-source-snapshot",
            "summary": "Source snapshot on subscription",
            "payload": {
              "e": "status",
              "E": 1788220801000,
              "status": "operational",
              "severity": "info",
              "scope": {
                "system": true
              },
              "occurredAt": 1788220801000,
              "sourceStates": [
                {
                  "id": "binance/realtime_ws/exchange",
                  "scope": {
                    "exchange": "binance",
                    "capability": "realtime_ws"
                  },
                  "state": "ok",
                  "freshness": "fresh",
                  "enabled": true,
                  "observedAt": 1788220800000,
                  "changedAt": 1788220000000,
                  "lastMessageAt": 1788220799500
                },
                {
                  "id": "bitbank/history_rest/pair/btc_jpy/1m",
                  "scope": {
                    "exchange": "bitbank",
                    "capability": "history_rest",
                    "pair": "btc_jpy",
                    "interval": "1m"
                  },
                  "state": "isolated",
                  "freshness": "fresh",
                  "reason": "upstream_unavailable",
                  "base": "BTC",
                  "quote": "JPY",
                  "enabled": true,
                  "observedAt": 1788220800000,
                  "changedAt": 1788220500000,
                  "lastSuccess": 1788219900000,
                  "nextProbe": 1788220830000
                }
              ],
              "sourceView": {
                "schemaVersion": 1,
                "publisherId": "api-1",
                "epoch": "c3f9a1",
                "revision": 42,
                "kind": "snapshot",
                "complete": true,
                "observedAt": 1788220800000
              }
            }
          },
          {
            "name": "status-source-change",
            "summary": "One source recovered",
            "payload": {
              "e": "status",
              "E": 1788220861000,
              "status": "operational",
              "severity": "info",
              "scope": {
                "system": true
              },
              "occurredAt": 1788220861000,
              "sourceChange": {
                "current": {
                  "id": "bitbank/history_rest/pair/btc_jpy/1m",
                  "scope": {
                    "exchange": "bitbank",
                    "capability": "history_rest",
                    "pair": "btc_jpy",
                    "interval": "1m"
                  },
                  "state": "healthy",
                  "freshness": "fresh",
                  "base": "BTC",
                  "quote": "JPY",
                  "enabled": true,
                  "observedAt": 1788220860000,
                  "changedAt": 1788220860000,
                  "lastSuccess": 1788220860000
                },
                "previousState": "isolated",
                "recovery": true
              },
              "sourceView": {
                "schemaVersion": 1,
                "publisherId": "api-1",
                "epoch": "c3f9a1",
                "revision": 43,
                "kind": "change",
                "complete": false,
                "observedAt": 1788220860000
              }
            }
          }
        ]
      },
      "error": {
        "name": "error",
        "title": "Errors",
        "description": "Subscription validation errors preserve the existing subscriptions and keep the connection open. `subscribe_failed` is followed by disconnection. Malformed JSON or messages exceeding 64 KiB may close the connection without a JSON error.\n\n| code | Meaning |\n| --- | --- |\n| `invalid_subscription` | Invalid quote, channel, symbol, or interval. |\n| `subscription_limit` | More than 100 subscriptions on this connection. |\n| `symbol_limit` | Symbol quota exceeded, or conflicting all-asset subscription. |\n| `unknown_message` | Unsupported message type. |\n| `subscribe_failed` | Realtime event service unavailable; reconnect with backoff. |",
        "payload": {
          "$ref": "#/components/schemas/Error"
        },
        "examples": [
          {
            "name": "error-1",
            "payload": {
              "type": "error",
              "code": "symbol_limit",
              "message": "a quote-only market subscription uses the full symbol budget; use a separate connection"
            }
          }
        ]
      },
      "subscribed": {
        "name": "subscribed",
        "title": "Subscription accepted",
        "description": "Acknowledges the accepted subscription ID. A missing or blank request ID becomes `default`.",
        "payload": {
          "$ref": "#/components/schemas/Subscribed"
        },
        "examples": [
          {
            "name": "subscribed",
            "payload": {
              "type": "subscribed",
              "subscription_id": "btc-jpy"
            }
          }
        ]
      },
      "unsubscribed": {
        "name": "unsubscribed",
        "title": "Subscription removed",
        "description": "Acknowledges the removed ID, including an ID that did not exist.",
        "payload": {
          "$ref": "#/components/schemas/Unsubscribed"
        },
        "examples": [
          {
            "name": "unsubscribed",
            "payload": {
              "type": "unsubscribed",
              "subscription_id": "btc-jpy"
            }
          }
        ]
      },
      "pong": {
        "name": "pong",
        "title": "Heartbeat reply",
        "description": "Echoes the application ping value in `ts`; it is not a WebSocket protocol pong frame.",
        "payload": {
          "$ref": "#/components/schemas/Pong"
        },
        "examples": [
          {
            "name": "pong",
            "payload": {
              "type": "pong",
              "ts": "1787974923000"
            }
          }
        ]
      }
    }
  },
  "x-codeSamples": [
    {
      "lang": "javascript",
      "source": "const url = new URL('wss://api.coinpost.ai/oracle/v2/ws')\n// url.searchParams.set('token', '<token>')\nconst socket = new WebSocket(url)\n\nsocket.addEventListener('open', () => {\n  socket.send(JSON.stringify({\n    type: 'subscribe',\n    id: 'btc-jpy',\n    symbol: 'BTC',\n    quote: 'JPY',\n    channels: ['klines'],\n    intervals: ['1m'],\n  }))\n})\n\nsocket.addEventListener('message', ({ data }) => {\n  console.log(JSON.parse(data))\n})"
    }
  ]
}
