AArtery
On this page

WebSocket Channel Catalog

This page enumerates every channel exposed on /v1/stream. For the wire protocol (subscribe / unsubscribe / ping / event frames) see the streaming concept; for the internal architecture see streaming architecture.

Endpoint & envelope

wss://api.artery.questflow.ai/v1/stream?token=art_live_<your-key>

Every server-pushed message is one of:

jsonc// → subscribe ack
{ "type": "subscribed", "subscription_id": "sub_01HMQ5..." }
 
// → event frame
{
  "type": "event",
  "subscription_id": "sub_01HMQ5...",
  "data": {
    "type": "<channel-specific envelope type>",
    "provider": "polymarket" | "kalshi" | "hyperliquid_perp" | "hyperliquid_hip4",
    "marketId": "<provider market id or universalId>",
    "payload": { /* channel-specific */ },
    "receivedAt": 1747123338220
  }
}
 
// → heartbeat (every 30s)
{ "type": "pong" }
 
// → error
{ "type": "error", "error": "<message>" }

All channels require the stream scope. The admin scope grants implicit access to every channel including private user-trade feeds.

Channel categories

CategoryPatternCardinality
Universal event spreadartery:stream:event:<universalId>One channel per event
Universal event arbitrageartery:stream:arbitrage:<universalId>One channel per event
Per-event tradesartery:stream:trade:<universalId>One channel per event
Per-provider trade firehoseartery:stream:trade:<provider>4 channels (one per provider)
Per-org private fillsartery:stream:user-trade:<orgId>One channel per tenant
Per-event anomalyartery:stream:anomaly:<universalId>One channel per event
Anomaly firehose (by kind)artery:stream:anomaly:<kind>4 channels (one per kind)
Anomaly firehose (all)artery:stream:anomaly:all1 global channel
UMA dispute observationsartery:stream:settlement_dispute:<universalId>One channel per event
Settlement landedartery:stream:settlement:<universalId>One channel per event
Raw upstream feedsartery:stream:<provider>:<providerMarketId>Per upstream feed

Channel reference


artery:stream:event:<universalId>

Cross-platform spread snapshots for one universal event.

FieldValue
Scopestream
data.type"event.spread"
PayloadSpreadSnapshot — same shape as GET /v1/events/:id/spread
CadenceEvery 5s tick and push-driven from upstream WS, debounced — only republishes when at least one leg's yesMid moved ≥ $0.005
jsonc{
  "type": "event",
  "data": {
    "type": "event.spread",
    "provider": "polymarket",
    "marketId": "evt_01HMPKW8...",
    "payload": {
      "universalId": "evt_01HMPKW8...",
      "legs": [
        { "provider": "polymarket", "yesBid": 0.42, "yesAsk": 0.45, "yesMid": 0.435 },
        { "provider": "kalshi", "yesBid": 0.39, "yesAsk": 0.42, "yesMid": 0.405 }
      ],
      "fetchedAt": "2026-05-13T08:42:18Z"
    },
    "receivedAt": 1747123338220
  }
}

artery:stream:arbitrage:<universalId>

Fires only when a profitable cross-provider or negation-pair arbitrage edge is detected on the latest spread snapshot.

FieldValue
Scopestream
data.type"event.arbitrage"
PayloadCrossProviderArbitrage | NegationPairArbitrage — same shape as GET /v1/events/:id/arbitrage
TriggerOn every spread tick that yields netEdge > 0

Inspect payload.kind to distinguish: "cross_provider" has buyProvider / sellProvider; "negation_pair" has yesProvider / noProvider.


artery:stream:trade:<universalId>

Public per-event trade fanout. Every observed fill across every linked market on this universal event.

FieldValue
Scopestream
data.type"trade.observed"
PayloadTradeRecord — same shape as GET /v1/events/:id/trades[]
TriggerEach Polymarket Exchange.OrderFilled (Polygon) + each Hyperliquid trades WS event
jsonc{
  "type": "event",
  "data": {
    "type": "trade.observed",
    "provider": "polymarket",
    "marketId": "evt_01HMPKW8...",
    "payload": {
      "provider": "polymarket",
      "marketId": "0xCONDITION_ID",
      "ts": "2026-05-13T08:50:01.220Z",
      "side": "buy",
      "price": 0.421,
      "qty": 1500,
      "maker": "0xabc...",
      "taker": "0xdef...",
      "txHash": "0x123...",
      "logIndex": 4
    },
    "receivedAt": 1747123801222
  }
}

artery:stream:trade:<provider>

Per-provider trade firehose. Same payload shape as the per-event channel, but unfiltered by event. Suitable for ETL pipelines.

PatternProvider
artery:stream:trade:polymarketPolymarket Polygon
artery:stream:trade:hyperliquid_perpHL perps
artery:stream:trade:hyperliquid_hip4HL HIP-4 binary
artery:stream:trade:kalshiKalshi (planned)

artery:stream:user-trade:<orgId>

Private per-org channel. Mirrors every fill where one of the org's bound wallets was maker or taker.

FieldValue
Scopestream (auth checks orgId matches caller's org)
data.type"user.fill"
PayloadTradeRecord (same as /v1/me/fills[])
TriggerEach upstream trade whose maker or taker matches any customer_wallets.address for orgId

Subscribers to other orgs' channels receive { "type": "error", "error": "forbidden_scope" } on subscribe.


artery:stream:anomaly:<universalId>

Per-event anomaly stream. Fires for all four kinds (trade_volume_spike, spread_widening, wallet_burst, settlement_delay) — filter client-side on payload.kind.

FieldValue
Scopestream
data.type"anomaly.detected"
PayloadAnomalyRecord (same as /v1/events/:id/anomaly-history[])
TriggerOn each detection by any of the four detectors

artery:stream:anomaly:<kind> (firehose)

Global anomaly firehose, partitioned by kind:

ChannelKind
artery:stream:anomaly:trade-volumetrade_volume_spike
artery:stream:anomaly:spread-wideningspread_widening
artery:stream:anomaly:wallet-burstwallet_burst
artery:stream:anomaly:settlement-delaysettlement_delay
Warning

The channel suffix uses kebab-case (trade-volume, spread-widening) but the payload.kind field uses snake_case (trade_volume_spike, spread_widening). Don't compare the channel suffix to payload.kind directly.

Plus artery:stream:anomaly:all — every anomaly regardless of kind.


artery:stream:settlement_dispute:<universalId>

UMA on-chain dispute observations for Polymarket-linked events.

FieldValue
Scopestream
data.type"settlement.dispute"
PayloadDisputePhaseRecord (same shape as /v1/events/:id/dispute-history[])
TriggerEach propose / dispute / settle UMA event seen by the Polygon RPC listener

Plus the provider-firehose alias artery:stream:settlement_dispute:polymarket.


artery:stream:settlement:<universalId>

Fires once when a provider resolution for the event is observed (the authoritative payoff signal — typically Kalshi settled or HL HIP-4 resolveOutcome).

FieldValue
Scopestream
data.type"settlement.landed"
PayloadResolutionRecord (same shape as /v1/events/:id/settlement.records[])
TriggerOnce per provider per event when settlement is recorded

Downstream services (e.g. PnlFinalizationService) consume this to mark fills realized.


Raw upstream feeds (advanced)

For callers that want the upstream payload pre-Artery-normalization:

ChannelWhat
artery:stream:hyperliquid_perp:*All HL allMids updates (~50ms cadence)
artery:stream:hyperliquid_perp:<coin>HL l2Book for one coin
artery:stream:hyperliquid_hip4:*All HL HIP-4 binary-outcome updates
artery:stream:polymarket:<token_id>Polymarket price_change + last_trade_price per token
artery:stream:kalshi:<ticker>Kalshi orderbook_delta (planned, RSA-gated)

The payload here is the upstream JSON shape, not Artery's normalized type. See per-provider notes in the streaming concept.

Subscription rules

  • Channels are flat strings — no glob expansion is supported in subscribe (use * only as the explicit channel suffix for provider firehoses).
  • Multiple subscriptions are independent; unsubscribing one doesn't affect the others.
  • Up to 1,000 active subscriptions per connection (subscription_limit error on overflow).
  • Concurrent connections per key follow your tier table.

Backpressure

LayerBoundDrop policy
Per-connection send queue5 MiBdrop oldest, then close on overflow
Per-connection subs1000reject new subscribe with subscription_limit
Heartbeat idle60sserver closes connection (code 1001)

See also

Edit this page on GitHubLast updated
WebSocket Channel Catalog · Artery API Docs