PocketScribe is a producer of indexed data. APIs that expose this data to humans and applications are separate components β they consume PocketScribe's Postgres + NATS firehose. This doc describes what we support architecturally, even though we don't ship those components.
PocketScribe guarantees that its Postgres schema and NATS subjects are stable, well-documented, and queryable by any tool that speaks SQL or NATS. We do not:
- Build a custom REST/GraphQL server.
- Bundle Hasura/PostgREST in our deployment.
- Provide client SDKs (other than the eventual
pkg/scribeclientGo SDK).
This separation lets the API layer evolve independently β different teams, different rates, different stacks.
Hasura is a Haskell-based GraphQL engine that auto-generates a complete GraphQL API from a Postgres schema. PocketScribe-friendly because:
- Reads
COMMENT ON TABLE/COMMENT ON COLUMNand surfaces them as GraphQL descriptions. - Supports read replicas (queries to replicas, mutations + subscriptions to primary).
- Streaming subscriptions (cursor-based polling, works against replicas) β usable for ~1-2s real-time without LISTEN/NOTIFY.
- Auto-permissions per role (RLS-aware).
What we ship to make Hasura easy:
- Stable
*_historytables and*views. COMMENT ONeverywhere (auto-docs).- A canonical metadata snapshot at
configs/downstream/hasura-metadata.yaml.
What we do not rely on:
- Hasura LISTEN/NOTIFY subscriptions (tied to primary; defeats replica scaling).
- Hasura Actions (custom resolvers; pushes logic into Hasura, fragile).
PostgREST is a Haskell-based REST API auto-generator over Postgres. Complementary to Hasura:
- REST contracts often easier than GraphQL for simple integrations.
- Auto-generates OpenAPI 3.0 from the schema (
GET /returns the spec). - Reads
COMMENT ONfor descriptions in OpenAPI. - Embeddable relations via query params (
?select=*,supplier(*)).
We can run both Hasura and PostgREST against the same database. No conflict.
For sub-second push (where Hasura streaming subscriptions' 1-2s polling latency is too slow), expose NATS subjects via a WebSocket bridge β a small Go service that:
- Subscribes to filtered NATS subjects.
- Forwards JSON-serialized events to WebSocket clients.
- Handles client subscriptions (per-subject, per-filter).
- Authenticates clients (JWT, API key, depends on deployment).
Why this over Hasura subscriptions: latency. NATS publish β bridge β WebSocket round-trip is <100ms. Hasura streaming polls Postgres every N seconds.
Why this over Postgres LISTEN/NOTIFY: doesn't scale; ties subscriptions to the write primary.
Implementation: a separate small repo (not PocketScribe). The contract is the NATS subject schema, documented below.
PocketScribe publishes to these subjects. Any downstream may subscribe:
pokt.block.{height} # full block payload (FinalizeBlock req/res + KV changes)
pokt.kv.{store}.{height} # per-store KV writes
pokt.events.{event_type}.{height} # per-event-type fan-out
Stability: subjects are versioned implicitly through the payload's proto_version field. We don't change subject names; payload schema evolves (always backwards-compatible adds).
Retention: 30 days default. Downstream that needs longer must keep its own storage.
Dedup: messages carry Nats-Msg-Id. Two redundant publishers (HA active-active) get dedup'd within a 24h window.
JSON envelope (preferred for WebSocket bridge clients):
{
"subject": "pokt.events.EventClaimSettled.487231",
"block_height": 487231,
"block_time": "2026-05-22T14:00:00Z",
"proto_version": "v0_1_5",
"payload": { /* event-specific fields */ }
}When all three layers run, expose documentation under one site:
docs.pocket-indexer.io/
βββ REST API β Redoc / Stoplight Elements over PostgREST's OpenAPI
βββ GraphQL API β GraphiQL or GraphQL Voyager
βββ Realtime β Markdown describing NATS subjects + sample WS code
All three drive descriptions from COMMENT ON in the database β single source of truth.
- Speed of iteration β API layer changes (auth, rate limiting, custom views) shouldn't require PocketScribe releases.
- Team boundaries β frontend team may own Hasura config; SRE may own NATS bridge; indexer team owns this repo.
- Choice for downstream β some users may prefer to write their own thin SDK over the Postgres views; that's fine.
- Track an
aggregate_statusfilter so onlystatus='public'aggregates are exposed. - Use
read_replicasconfiguration to direct queries to read-only Postgres replicas. - Avoid Hasura's "Actions" feature β keeps the API layer thin.
- Sync metadata via
hasura metadata applyin CI.
- Use
db-anon-role = 'web_anon'for unauthenticated reads. - Authenticated roles via JWT (
db-jwt-secret). - Limit query depth / row count to prevent DoS (
db-max-rows). - Cache responses with
Cache-Control: public, max-age=Non sealed-bucket aggregates (immutable until reseal).
- Subscribe to subjects with consumer group per client session.
- Heartbeat every 30s to detect disconnected clients.
- Backpressure: if client can't keep up, drop messages (with lag metric) β don't queue indefinitely.
aggregate_registry.statusfor filtering exposed aggregates.safe_heightview for cross-entity-consistency queries.bucket_sealtable for "is this aggregate bucket trustworthy?" checks.consumer_consolidationfor per-consumer lag dashboards.- Stable NATS subjects with documented payload schemas.
- Stable Postgres views (
supplier,application, etc.) that abstract*_history.
- HTTP server, GraphQL/REST endpoints.
- Authentication / authorization (lives in downstream).
- Rate limiting (lives at API gateway / Hasura / PostgREST config).
- Cache invalidation (responsibility of downstream cache layer).
- Custom business logic (lives in downstream).
- ADR-011 (downstream APIs out of scope) β design rationale.
docs/operations/development-workflow.mdβ how Hasura is wired in Tilt for dev verification.