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.
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
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:
- Admin GraphQL API at
/graphiql/admin - Client admin UI at
https://<client-url>/lexicons(you must log in with an admin DID)
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:
# 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
| Endpoint | Description |
|---|---|
/graphql | Public GraphQL API |
/graphql/ws | GraphQL subscriptions (WebSocket) |
/admin/graphql | Admin GraphQL API |
/graphiql | GraphQL playground (public API) |
/graphiql/admin | GraphQL playground (admin API) |
/health | Health check |
/stats | Server 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
| Variable | Example | What it is for |
|---|---|---|
HOST | 0.0.0.0 | Makes the app reachable in container runtime |
PORT | 8080 | App port |
DATABASE_URL | sqlite:/data/hypergoat.db | Main indexed-records database |
EXTERNAL_BASE_URL | https://hyperindex.example.com | Public backend URL used by frontend/admin flows and GraphiQL links |
SECRET_KEY_BASE | <long-random-secret> | Session/signing secret |
ADMIN_DIDS | did:plc:... | DIDs with admin privileges |
TRUST_PROXY_HEADERS | true | Trust forwarded auth headers from client proxy |
TAP_ENABLED | true | Enables Tap mode |
TAP_URL | ws://tap.railway.internal:2480 | Tap websocket endpoint |
TAP_ADMIN_PASSWORD | <same-as-tap-service> | Tap admin auth secret |
TAP_DISABLE_ACKS | true or false | Controls ACK behavior for Tap consumer; set to true if ACK mode causes websocket reconnect/drop loops |
TAP_DISABLE_ACKSis configured on the backend/indexer service. Note onTAP_DISABLE_ACKSIn 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, setTAP_DISABLE_ACKS=trueon the Hyperindex backend to stabilize ingestion first, then investigate Tap resource/config compatibility before re-enabling ACK mode.
Tap service
| Variable | Example | What it is for |
|---|---|---|
TAP_DATABASE_URL | sqlite:///data/tap.db | Persists Tap cursor/state |
TAP_ADMIN_PASSWORD | <shared-secret> | Protects Tap admin routes |
TAP_COLLECTION_FILTERS | app.gainforest.*,app.certified.*,org.hypercerts.* | Filters ingested record collections |
TAP_SIGNAL_COLLECTION | app.certified.actor.profile | Signal collection for repo discovery |
TAP_FULL_NETWORK | true / false | Full network tracking toggle |
TAP_FULL_NETWORK=trueenables full-network tracking and triggers a broad historical backfill across discoverable repos.
Client (Next.js)
| Variable | Example | What it is for |
|---|---|---|
NEXT_PUBLIC_API_URL | https://hyperindex.example.com | Browser-side URL of your Hyperindex backend |
HYPERINDEX_URL | https://hyperindex.example.com | Server-side URL of your Hyperindex backend (used by Next API proxy routes) |
PUBLIC_URL | https://hyperindex-frontend.example.com | Client 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_PARALLELISMTAP_RESYNC_PARALLELISMTAP_OUTBOX_PARALLELISMTAP_MAX_DB_CONNSTAP_OUTBOX_CAPACITYTAP_NO_REPLAYTAP_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_NETWORKbelong to TapTAP_DISABLE_ACKS,TAP_ENABLED,TAP_URLbelong to Hyperindex backend
Client works but admin requests fail
HYPERINDEX_URLis missing on the client deploymentNEXT_PUBLIC_API_URLalone is not enough for server-side proxy routes
admin privileges requiredwhile logged inTRUST_PROXY_HEADERSis missing/false- Logged-in DID is not present in
ADMIN_DIDS
Trailing slash URL issues
PUBLIC_URLmust 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/healthroute
- Backend healthcheck should be
Deploy on Railway
1) Deploy Hyperindex backend
- Create a Railway service from the repository
- Attach a persistent volume mounted to
/data - Set healthcheck path to
/health - Add backend variables from the baseline list above
- Deploy
2) Deploy Tap
- Create a Railway service from image:
ghcr.io/bluesky-social/indigo/tap:latest(or a pinned tag) - Attach a persistent volume mounted to
/data - Add Tap variables from the baseline list above
- Deploy
3) Connect backend to Tap
Set on backend service:
TAP_ENABLED=trueTAP_URL=ws://<tap-private-host>:2480TAP_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
docker compose up --build
Learn more
- GitHub repository — source code, issues, and documentation
- Indexers & Discovery — how indexers fit into the Hypercerts architecture
- Building on Hypercerts — integration patterns for platforms and tools