Skip to content

Hyperindex

Hyperindex (hi) is a Go AT Protocol AppView server that indexes records and exposes them via GraphQL. Use it to:

  • Index all hypercert-related records from the ATProto network in real time
  • Query indexed data through a typed GraphQL API
  • Backfill historical records from any user or the entire network
  • Run your own indexer for full control over data availability and query performance

Built in Go on bluesky-social/indigo. Source: github.com/hypercerts-org/hyperindex.

How it works

Hyperindex is Tap-first (recommended). Tap handles ingestion and Hyperindex consumes Tap events, stores records, and exposes them via GraphQL.

text
ATProto Relay ──→ Tap ──→ Hyperindex Consumer ──→ Records DB ──→ GraphQL API

                               Activity Log ──→ Admin Dashboard

Jetstream mode still exists as a legacy/non-Tap mode, but Tap is the preferred setup.

Quick start

Terminal
git clone https://github.com/hypercerts-org/hyperindex.git
cd hyperindex
cp .env.example .env
go run ./cmd/hyperindex

Open http://localhost:8080/graphiql/admin to access the admin interface.

Register lexicons

Lexicons define the AT Protocol record types you want to index. You can register them via:

  1. Admin GraphQL API at /graphiql/admin
  2. Client admin UI at https://<client-url>/lexicons (you must log in with an admin DID)
graphql
mutation {
  uploadLexicons(files: [...])  # Upload lexicon JSON files
}

Or place lexicon JSON files in a directory and set the LEXICON_DIR environment variable.

For hypercerts, you would register the org.hypercerts.claim.* lexicons — see Introduction to Lexicons for the full list.

Query via GraphQL

Access your indexed data at /graphql:

graphql
# Query records by collection
query {
  records(collection: "org.hypercerts.claim.activity") {
    edges {
      node {
        uri
        did
        value
      }
    }
  }
}


# With typed queries (when lexicon schemas are loaded)
query {
  orgHypercertsClaimActivity(first: 10) {
    edges {
      node {
        uri
        workScope
        startDate
        createdAt
      }
    }
  }
}


# With typed filter queries (title contains "Hypercerts")
query {
  orgHypercertsClaimActivity(
    first: 10
    where: { title: { contains: "Hypercerts" } }
  ) {
    edges {
      node {
        uri
        title
        createdAt
      }
    }
  }
}

Endpoints

EndpointDescription
/graphqlPublic GraphQL API
/graphql/wsGraphQL subscriptions (WebSocket)
/admin/graphqlAdmin GraphQL API
/graphiqlGraphQL playground (public API)
/graphiql/adminGraphQL playground (admin API)
/healthHealth check
/statsServer statistics

Deployment configuration

Use this section when deploying Hyperindex (backend, Tap, and client). It lists the environment variables you should set first for a reliable initial deployment, followed by optional variables for advanced tuning.

Baseline deployment variables

These are the variables you should set first to get a stable deployment running.

Hyperindex backend

VariableExampleWhat it is for
HOST0.0.0.0Makes the app reachable in container runtime
PORT8080App port
DATABASE_URLsqlite:/data/hypergoat.dbMain indexed-records database
EXTERNAL_BASE_URLhttps://hyperindex.example.comPublic backend URL used by frontend/admin flows and GraphiQL links
SECRET_KEY_BASE<long-random-secret>Session/signing secret
ADMIN_DIDSdid:plc:...DIDs with admin privileges
TRUST_PROXY_HEADERStrueTrust forwarded auth headers from client proxy
TAP_ENABLEDtrueEnables Tap mode
TAP_URLws://tap.railway.internal:2480Tap websocket endpoint
TAP_ADMIN_PASSWORD<same-as-tap-service>Tap admin auth secret
TAP_DISABLE_ACKStrue or falseControls ACK behavior for Tap consumer; set to true if ACK mode causes websocket reconnect/drop loops

TAP_DISABLE_ACKS is configured on the backend/indexer service. Note on TAP_DISABLE_ACKS

In some deployments, ACK mode (TAP_DISABLE_ACKS=false) can cause repeated websocket disconnect loops (for example: connection reset by peer, close 1006, frequent reconnect backoff). If you see that pattern, set TAP_DISABLE_ACKS=true on the Hyperindex backend to stabilize ingestion first, then investigate Tap resource/config compatibility before re-enabling ACK mode.

Tap service

VariableExampleWhat it is for
TAP_DATABASE_URLsqlite:///data/tap.dbPersists Tap cursor/state
TAP_ADMIN_PASSWORD<shared-secret>Protects Tap admin routes
TAP_COLLECTION_FILTERSapp.gainforest.*,app.certified.*,org.hypercerts.*Filters ingested record collections
TAP_SIGNAL_COLLECTIONapp.certified.actor.profileSignal collection for repo discovery
TAP_FULL_NETWORKtrue / falseFull network tracking toggle

TAP_FULL_NETWORK=true enables full-network tracking and triggers a broad historical backfill across discoverable repos.

Client (Next.js)

VariableExampleWhat it is for
NEXT_PUBLIC_API_URLhttps://hyperindex.example.comBrowser-side URL of your Hyperindex backend
HYPERINDEX_URLhttps://hyperindex.example.comServer-side URL of your Hyperindex backend (used by Next API proxy routes)
PUBLIC_URLhttps://hyperindex-frontend.example.comClient frontend URL used for OAuth client metadata and auth redirects
COOKIE_SECRET<long-random-secret>Session encryption
ATPROTO_JWK_PRIVATE<jwk-json>Confidential OAuth signing key

Optional variables

Set these only when needed.

Backend

  • ALLOWED_ORIGINS

Tap

  • TAP_FIREHOSE_PARALLELISM
  • TAP_RESYNC_PARALLELISM
  • TAP_OUTBOX_PARALLELISM
  • TAP_MAX_DB_CONNS
  • TAP_OUTBOX_CAPACITY
  • TAP_NO_REPLAY
  • TAP_REPO_FETCH_TIMEOUT

Client

  • Additional auth/provider-specific settings depending on deployment model

Common pitfalls

  • Wrong variable on wrong service

    • TAP_COLLECTION_FILTERS, TAP_SIGNAL_COLLECTION, TAP_FULL_NETWORK belong to Tap
    • TAP_DISABLE_ACKS, TAP_ENABLED, TAP_URL belong to Hyperindex backend
  • Client works but admin requests fail

    • HYPERINDEX_URL is missing on the client deployment
    • NEXT_PUBLIC_API_URL alone is not enough for server-side proxy routes
  • admin privileges required while logged in

    • TRUST_PROXY_HEADERS is missing/false
    • Logged-in DID is not present in ADMIN_DIDS
  • Trailing slash URL issues

    • PUBLIC_URL must not include a trailing slash.
    • Use:
      • https://hyperindex-frontend.example.com
      • https://hyperindex-frontend.example.com/
    • A trailing slash can cause OAuth client metadata lookup errors (for example: client_metadata not found).
  • Healthcheck confusion

    • Backend healthcheck should be /health
    • Frontend usually uses / unless you explicitly add a /health route

Deploy on Railway

1) Deploy Hyperindex backend

  1. Create a Railway service from the repository
  2. Attach a persistent volume mounted to /data
  3. Set healthcheck path to /health
  4. Add backend variables from the baseline list above
  5. Deploy

2) Deploy Tap

  1. Create a Railway service from image: ghcr.io/bluesky-social/indigo/tap:latest (or a pinned tag)
  2. Attach a persistent volume mounted to /data
  3. Add Tap variables from the baseline list above
  4. Deploy

3) Connect backend to Tap

Set on backend service:

  • TAP_ENABLED=true
  • TAP_URL=ws://<tap-private-host>:2480
  • TAP_ADMIN_PASSWORD=<same-as-tap-service>

Redeploy backend after updating these values.

4) Deploy client (Next.js)

Deploy client on Railway/Vercel/etc and set:

  • NEXT_PUBLIC_API_URL=<hyperindex-backend-public-url>
  • HYPERINDEX_URL=<hyperindex-backend-public-url>
  • auth/session vars (PUBLIC_URL, COOKIE_SECRET, ATPROTO_JWK_PRIVATE) as needed

Railway-specific DB path notes

For mounted volumes at /data:

  • Backend SQLite:
    • DATABASE_URL=sqlite:/data/hypergoat.db
  • Tap SQLite:
    • TAP_DATABASE_URL=sqlite:///data/tap.db

Running with Docker

Terminal
docker compose up --build

Learn more