hydrant docs
hydrant docsfor hydrant 0.1.0
hydrant 0.1.0

available in builds compiled with the indexer_stream feature. /stream is hydrant's ordered websocket event stream. each subscriber receives a full copy of the stream.

/stream is not wire- or protocol-compatible with tap. tap uses a sharded work queue, server-managed acknowledgements, and redelivery; hydrant uses broadcast delivery and client-managed cursors.

GET /stream

subscribe to the ordered event stream.

query parameters

param type description
cursor integer inclusive u64 event ID from which to replay durable record events.

if cursor is omitted, the connection starts at the current stream head and receives only new events. if it is present, hydrant replays durable record events whose IDs are greater than or equal to the cursor, up to the head observed when the connection starts, then switches to live delivery.

cursor=0 replays all retained record events. IDs are monotonic but may have gaps: live-only identity and account events consume IDs without being persisted, and events that cannot be inflated are skipped.

event envelope

events are JSON text frames with this envelope:

{
  "id": 42,
  "type": "record",
  "record": {}
}
field type description
id integer u64 stream event ID.
type string record, identity, or account.
record object present when type is record.
identity object present when type is identity.
account object present when type is account.

only the payload matching type is present.

record

record events are durable and available through cursor replay.

{
  "id": 42,
  "type": "record",
  "record": {
    "live": true,
    "did": "did:plc:abc123xyz",
    "rev": "3kpjxabc123",
    "collection": "app.bsky.feed.post",
    "rkey": "3kpjxabc123",
    "action": "create",
    "record": {
      "text": "hello, world!",
      "$type": "app.bsky.feed.post",
      "createdAt": "2026-05-27T12:00:00.000Z"
    },
    "cid": "bafyreihy..."
  }
}
field type description
live boolean true for live ingestion; false for backfill.
did string repository DID.
rev string repository revision TID.
collection string collection NSID.
rkey string record key.
action string create, update, or delete.
record object decoded DAG-CBOR record, its keys in the order the record's CBOR has them (shorter first), not sorted; omitted when no record content is stored.
cid string CID of record; omitted when no record content is stored.

record and cid are omitted for deletes and when hydrant is configured not to store record content, including HYDRANT_ONLY_INDEX_LINKS=true.

a redaction stops a body from being served once it completes, except in events already queued for a reader at that moment.

identity

identity events are live-only and are not available through cursor replay.

{
  "id": 43,
  "type": "identity",
  "identity": {
    "did": "did:plc:abc123xyz",
    "handle": "user.bsky.social"
  }
}
field type description
did string repository DID.
handle string current handle; omitted when unavailable.

account

account events are live-only and are not available through cursor replay.

{
  "id": 44,
  "type": "account",
  "account": {
    "did": "did:plc:abc123xyz",
    "active": false,
    "status": "deactivated"
  }
}
field type description
did string repository DID.
active boolean whether the repository is active.
status string account status; omitted when unavailable.

websocket behavior

  • the server sends an empty ping every 30 seconds and responds to client ping frames with pong frames.
  • client text and binary frames are not accepted; hydrant closes the connection when it receives one.
  • when /stream is in HYDRANT_THROTTLED_STREAMS (it isn't by default), a replay from a cursor runs at most HYDRANT_STREAM_REPLAY_SPEED times the bandwidth the live stream would take (10 by default), or HYDRANT_STREAM_REPLAY_FLOOR_BYTES a second (about 80 Mbit/s) when that's more. the live tail never waits on it.
  • a subscriber that falls behind the live broadcast catches up from the database at its own pace. record events are never skipped, but identity and account events it fell behind on are not replayed.
  • if hydrant can't read the next events from the database, it closes the connection rather than skip them. reconnect with the last id you received as the cursor.
  • if sending blocks for HYDRANT_STREAM_SEND_TIMEOUT (30 seconds by default), hydrant sends an error text frame and closes the connection:
{
  "type": "error",
  "error": "ConsumerTooSlow",
  "message": "stream socket send blocked for at least 30 seconds"
}

GET /stream/head

get the ID of the newest durable record event.

{ "id": 289455 }

id is null while no events are stored. identity and account events consume IDs without being persisted, so this is the newest ID a cursor replay can deliver, not the ID counter.

a consumer can snapshot the head before subscribing with cursor=0 and treat the replay as caught up once it has seen a frame with id greater than or equal to the snapshot. keep an idle-timeout fallback: if the head event itself cannot be inflated, it is skipped and never delivered.