···11# HappyView
2233-HappyView is the best way to build an [AppView](https://atproto.com/guides/glossary#app-view) for the [AT Protocol](https://atproto.com). Upload your [lexicon](reference/glossary#at-protocol-terms) schemas and get a fully functional AppView, complete with [XRPC](reference/glossary#at-protocol-terms) endpoints, OAuth, real-time network sync, and historical [backfill](guides/backfill), without writing a single line of server code.
33+HappyView is the best way to build an [AppView](https://atproto.com/guides/glossary#app-view) for the [AT Protocol](https://atproto.com). Upload your [lexicon](reference/glossary.md#at-protocol-terms) schemas and get a fully functional AppView, complete with [XRPC](reference/glossary.md#at-protocol-terms) endpoints, OAuth, real-time network sync, and historical [backfill](guides/backfill.md), without writing a single line of server code.
4455Building an AppView from scratch means wiring up firehose connections, record storage, XRPC routing, OAuth flows, and PDS write proxying before you can even think about your application. HappyView handles all of that. Define your data model with lexicons, add custom logic with Lua scripts when you need it, and ship your app.
66···27272828## Next Steps
29293030-- [Quickstart](getting-started/deployment/railway): Deploy HappyView on Railway or run it locally
3131-- [Lexicons](guides/lexicons): Upload lexicon schemas and start indexing records
3232-- [Lua Scripting](guides/scripting): Write custom query and procedure logic
3030+- [Quickstart](getting-started/deployment/railway.md): Deploy HappyView on Railway or run it locally
3131+- [Lexicons](guides/lexicons.md): Upload lexicon schemas and start indexing records
3232+- [Lua Scripting](guides/scripting.md): Write custom query and procedure logic
+2-2
docs/getting-started/authentication.md
···19192020## Getting a token from the dashboard
21212222-The easiest way to get a token for CLI or curl usage is through the [web dashboard](dashboard):
2222+The easiest way to get a token for CLI or curl usage is through the [web dashboard](dashboard.md):
232324241. Open the dashboard and log in with your AT Protocol identity
25252. Open your browser's developer tools (F12 or Cmd+Shift+I)
···62626363Admin endpoints require the authenticated user's DID to exist in the `admins` table. If the table is empty (fresh deployment), the first authenticated request to any admin endpoint auto-bootstraps that user as the initial admin.
64646565-To add more admins, use `POST /admin/admins` or the [dashboard](dashboard). See [Admin API](../reference/admin-api#admin-management) for details.
6565+To add more admins, use `POST /admin/admins` or the [dashboard](dashboard.md). See [Admin API](../reference/admin-api.md#admin-management) for details.
+2-2
docs/getting-started/configuration.md
···11# Configuration
2233-HappyView is configured via environment variables. A `.env` file in the project root is loaded automatically on startup. See [Deployment](deployment/docker) for local setup or [Production Deployment](../reference/production-deployment) for production setup.
33+HappyView is configured via environment variables. A `.env` file in the project root is loaded automatically on startup. See [Deployment](deployment/docker.md) for local setup or [Production Deployment](../reference/production-deployment.md) for production setup.
4455## Environment variables
66···1212| `PORT` | no | `3000` | Bind port |
1313| `TAP_URL` | no | `http://localhost:2480` | [Tap](https://github.com/bluesky-social/indigo/tree/main/cmd/tap) instance URL for real-time record streaming and backfill |
1414| `TAP_ADMIN_PASSWORD` | no | --- | Shared secret for authenticating with Tap's admin endpoints |
1515-| `RELAY_URL` | no | `https://bsky.network` | Relay URL for [backfill](../guides/backfill) repo discovery |
1515+| `RELAY_URL` | no | `https://bsky.network` | Relay URL for [backfill](../guides/backfill.md) repo discovery |
1616| `PLC_URL` | no | `https://plc.directory` | [PLC directory](https://github.com/did-method-plc/did-method-plc) URL for DID resolution |
1717| `RUST_LOG` | no | `happyview=debug,tower_http=debug` | Log filter (uses `tracing_subscriber::EnvFilter`) |
1818
+3-3
docs/getting-started/dashboard.md
···11# Dashboard
2233-HappyView ships with a web dashboard that provides a visual interface for everything the [admin API](../reference/admin-api) offers: managing lexicons, viewing indexed records, and monitoring backfill jobs. It runs as a separate Next.js application alongside the Rust backend.
33+HappyView ships with a web dashboard that provides a visual interface for everything the [admin API](../reference/admin-api.md) offers: managing lexicons, viewing indexed records, and monitoring backfill jobs. It runs as a separate Next.js application alongside the Rust backend.
4455## Logging in for the first time
66···21212222Toggle **Enable backfill** to index historical records when uploading a record-type lexicon.
23232424-**Network** lexicons are fetched from the AT Protocol network. Enter an NSID (e.g. `xyz.statusphere.status`) and HappyView resolves the schema automatically. If found, the lexicon JSON is displayed in a read-only editor. Click **Add** to track it. Network lexicons are kept up to date via Tap. See [Lexicons - Network lexicons](../guides/lexicons#network-lexicons) for how resolution works.
2424+**Network** lexicons are fetched from the AT Protocol network. Enter an NSID (e.g. `xyz.statusphere.status`) and HappyView resolves the schema automatically. If found, the lexicon JSON is displayed in a read-only editor. Click **Add** to track it. Network lexicons are kept up to date via Tap. See [Lexicons - Network lexicons](../guides/lexicons.md#network-lexicons) for how resolution works.
25252626### JSON editor
2727···35353636The Lua editor provides context-aware code completions, including suggestions for the `Record`, `db`, `input`, and `params` APIs as well as Lua keywords, builtins, and standard library functions. It also offers snippet templates for common constructs like `if`, `for`, and `function`.
37373838-See [Lua Scripting](../guides/scripting) for the full runtime reference and examples.
3838+See [Lua Scripting](../guides/scripting.md) for the full runtime reference and examples.
+1-1
docs/getting-started/deployment/docker.md
···46464747## Next steps
48484949-Your HappyView stack is running. Follow the [Statusphere tutorial](../../tutorials/statusphere) to upload lexicons, add custom query logic, and start indexing records from the network.
4949+Your HappyView stack is running. Follow the [Statusphere tutorial](../../tutorials/statusphere.md) to upload lexicons, add custom query logic, and start indexing records from the network.
+3-3
docs/getting-started/deployment/other.md
···11# Local Development from Source
2233-This guide runs HappyView directly with `cargo run`, with you managing Postgres, AIP, and Tap separately. If you'd rather use Docker Compose to run everything together, see [Local Development with Docker](docker).
33+This guide runs HappyView directly with `cargo run`, with you managing Postgres, AIP, and Tap separately. If you'd rather use Docker Compose to run everything together, see [Local Development with Docker](docker.md).
4455## Prerequisites
66···2626TAP_ADMIN_PASSWORD=your-secret-here
2727```
28282929-See [Configuration](../configuration) for all available variables.
2929+See [Configuration](../configuration.md) for all available variables.
30303131## 2. Create the database
3232···52525353## Next steps
54545555-Your HappyView instance is running. Follow the [Statusphere tutorial](../../tutorials/statusphere) to upload lexicons, add custom query logic, and start indexing records from the network.
5555+Your HappyView instance is running. Follow the [Statusphere tutorial](../../tutorials/statusphere.md) to upload lexicons, add custom query logic, and start indexing records from the network.
+11-11
docs/getting-started/quickstart.md
···8899| Option | Best for |
1010|--------|----------|
1111-| [**Railway**](deployment/railway) | Fastest path — one-click deploy of the full stack (HappyView + AIP + Tap + Postgres) |
1212-| [**Docker Compose**](deployment/docker) | Local development with the full stack running in containers |
1313-| [**From source**](deployment/other) | Running HappyView with `cargo run` and managing dependencies yourself |
1111+| [**Railway**](deployment/railway.md) | Fastest path — one-click deploy of the full stack (HappyView + AIP + Tap + Postgres) |
1212+| [**Docker Compose**](deployment/docker.md) | Local development with the full stack running in containers |
1313+| [**From source**](deployment/other.md) | Running HappyView with `cargo run` and managing dependencies yourself |
14141515If you're just trying HappyView for the first time, start with Railway.
16161717## 2. Log in to the dashboard
18181919-Open your HappyView instance in a browser. The built-in [dashboard](dashboard) is served at the root URL.
1919+Open your HappyView instance in a browser. The built-in [dashboard](dashboard.md) is served at the root URL.
20202121Click **Log in** and authenticate with your AT Protocol identity. On a fresh deployment with no admins configured, the first authenticated request to any admin endpoint automatically bootstraps that user as an admin.
2222···31313232HappyView immediately starts indexing records for that collection. A backfill job is created to fetch historical records, and new records stream in via Tap in real time.
33333434-You can also upload lexicons manually via the dashboard or the [admin API](../reference/admin-api). See [Lexicons](../guides/lexicons) for the full details.
3434+You can also upload lexicons manually via the dashboard or the [admin API](../reference/admin-api.md). See [Lexicons](../guides/lexicons.md) for the full details.
35353636## 4. Verify records are being indexed
3737···4949GET /xrpc/xyz.statusphere.listStatuses?limit=5
5050```
51515252-For custom query logic, attach a [Lua script](../guides/scripting).
5252+For custom query logic, attach a [Lua script](../guides/scripting.md).
53535454## Next steps
55555656You now have a working AppView. Here's where to go from here:
57575858-- [**Statusphere tutorial**](../tutorials/statusphere): end-to-end walkthrough building a complete AppView with record, query, and procedure lexicons
5959-- [**Lexicons guide**](../guides/lexicons): target collections, backfill flag, network lexicons
6060-- [**Lua Scripting**](../guides/scripting): custom query and procedure logic
6161-- [**Configuration**](configuration): environment variables and tuning
6262-- [**Authentication**](authentication): how OAuth works and how to get API tokens
5858+- [**Statusphere tutorial**](../tutorials/statusphere.md): end-to-end walkthrough building a complete AppView with record, query, and procedure lexicons
5959+- [**Lexicons guide**](../guides/lexicons.md): target collections, backfill flag, network lexicons
6060+- [**Lua Scripting**](../guides/scripting.md): custom query and procedure logic
6161+- [**Configuration**](configuration.md): environment variables and tuning
6262+- [**Authentication**](authentication.md): how OAuth works and how to get API tokens
+6-6
docs/guides/backfill.md
···4455## When backfill runs
6677-- **Automatically** when a record-type lexicon is uploaded with `backfill: true` (the default). See [Lexicons - Backfill flag](lexicons#backfill-flag).
88-- **Manually** via `POST /admin/backfill` or the [dashboard](../getting-started/dashboard). You can scope a manual backfill to a specific collection, a specific DID, or both.
77+- **Automatically** when a record-type lexicon is uploaded with `backfill: true` (the default). See [Lexicons - Backfill flag](lexicons.md#backfill-flag).
88+- **Manually** via `POST /admin/backfill` or the [dashboard](../getting-started/dashboard.md). You can scope a manual backfill to a specific collection, a specific DID, or both.
991010-See the [admin API](../reference/admin-api#backfill) for endpoint details.
1010+See the [admin API](../reference/admin-api.md#backfill) for endpoint details.
11111212## How it works
1313···32323333## Next steps
34343535-- [Lexicons](lexicons#backfill-flag): Control whether lexicons trigger backfill on upload
3636-- [Admin API](../reference/admin-api#backfill): Full reference for backfill endpoints
3737-- [Admin API - Tap Stats](../reference/admin-api#tap-stats): Monitor Tap's processing progress
3535+- [Lexicons](lexicons.md#backfill-flag): Control whether lexicons trigger backfill on upload
3636+- [Admin API](../reference/admin-api.md#backfill): Full reference for backfill endpoints
3737+- [Admin API - Tap Stats](../reference/admin-api.md#tap-stats): Monitor Tap's processing progress
+8-8
docs/guides/lexicons.md
···2233Lexicons are the core building block of HappyView. They're [AT Protocol schema definitions](https://atproto.com/specs/lexicon) that describe your data model, and HappyView uses them to decide which records to index from the network and what XRPC endpoints to serve.
4455-You don't write route handlers or database queries; you upload a lexicon and HappyView generates the infrastructure from it. There are two ways to add lexicons: uploading them via the [admin API](../reference/admin-api#lexicons) or [dashboard](../getting-started/dashboard), or fetching them directly from the AT Protocol network via [DNS authority resolution](#network-lexicons).
55+You don't write route handlers or database queries; you upload a lexicon and HappyView generates the infrastructure from it. There are two ways to add lexicons: uploading them via the [admin API](../reference/admin-api.md#lexicons) or [dashboard](../getting-started/dashboard.md), or fetching them directly from the AT Protocol network via [DNS authority resolution](#network-lexicons).
6677## Supported lexicon types
88···1313| `procedure` | Registers a `POST /xrpc/{nsid}` endpoint that proxies writes to the user's PDS |
1414| `definitions` | Stored but does not generate routes or subscriptions |
15151616-A typical setup has three lexicons working together: a **record** lexicon that defines the data and triggers indexing, a **query** lexicon that exposes a read endpoint, and a **procedure** lexicon that exposes a write endpoint. The [Statusphere tutorial](../tutorials/statusphere) walks through this pattern end-to-end.
1616+A typical setup has three lexicons working together: a **record** lexicon that defines the data and triggers indexing, a **query** lexicon that exposes a read endpoint, and a **procedure** lexicon that exposes a write endpoint. The [Statusphere tutorial](../tutorials/statusphere.md) walks through this pattern end-to-end.
17171818## Target collection
1919···21212222For example, a query lexicon `xyz.statusphere.listStatuses` would set `target_collection` to `xyz.statusphere.status` to read from that record collection.
23232424-See the [admin API](../reference/admin-api#upload--upsert-a-lexicon) for how to set `target_collection` when uploading.
2424+See the [admin API](../reference/admin-api.md#upload--upsert-a-lexicon) for how to set `target_collection` when uploading.
25252626:::note
2727The `target_collection` is available in Lua scripts as the `collection` global, but it is not required if your endpoint uses a Lua script.
···35353636When record-type lexicons change (uploaded or deleted), HappyView automatically syncs the updated collection filter to Tap. HappyView always includes `com.atproto.lexicon.schema` in the filter to track network lexicon updates.
37373838-Deleting a lexicon updates Tap's collection filters (stopping live indexing for that collection) but does **not** remove previously indexed repos or their cached state from Tap. To fully reset a collection's state, delete the lexicon, re-add it, and run a [backfill](backfill).
3838+Deleting a lexicon updates Tap's collection filters (stopping live indexing for that collection) but does **not** remove previously indexed repos or their cached state from Tap. To fully reset a collection's state, delete the lexicon, re-add it, and run a [backfill](backfill.md).
39394040## Network lexicons
4141···78787979## Next steps
80808181-- [Lua Scripting](scripting): Add custom query and procedure logic to your endpoints
8282-- [XRPC API](../reference/xrpc-api): Understand how the generated endpoints behave
8383-- [Backfill](backfill): Learn how historical records are indexed
8484-- [Admin API](../reference/admin-api): Full reference for lexicon management endpoints
8181+- [Lua Scripting](scripting.md): Add custom query and procedure logic to your endpoints
8282+- [XRPC API](../reference/xrpc-api.md): Understand how the generated endpoints behave
8383+- [Backfill](backfill.md): Learn how historical records are indexed
8484+- [Admin API](../reference/admin-api.md): Full reference for lexicon management endpoints
+15-15
docs/guides/scripting.md
···270270end
271271```
272272273273-To see log output, make sure your `RUST_LOG` environment variable includes debug level for HappyView (the default `happyview=debug` works). See [Configuration](../getting-started/configuration).
273273+To see log output, make sure your `RUST_LOG` environment variable includes debug level for HappyView (the default `happyview=debug` works). See [Configuration](../getting-started/configuration.md).
274274275275### Error messages
276276···293293See the example script references for complete, ready-to-use scripts:
294294295295**Queries:**
296296-- [Get a record](../reference/scripts/get-record) — fetch a single record by AT URI
297297-- [Paginated list](../reference/scripts/paginated-list) — list records with cursor-based pagination and DID filtering
298298-- [List or fetch](../reference/scripts/list-or-fetch) — combined single-record lookup and paginated listing
299299-- [Expanded query](../reference/scripts/expanded-query) — list statuses with user profiles in a single response
296296+- [Get a record](../reference/scripts/get-record.md) — fetch a single record by AT URI
297297+- [Paginated list](../reference/scripts/paginated-list.md) — list records with cursor-based pagination and DID filtering
298298+- [List or fetch](../reference/scripts/list-or-fetch.md) — combined single-record lookup and paginated listing
299299+- [Expanded query](../reference/scripts/expanded-query.md) — list statuses with user profiles in a single response
300300301301**Procedures:**
302302-- [Create a record](../reference/scripts/create-record) — simple write that saves input as a record
303303-- [Upsert a record](../reference/scripts/upsert-record) — create or update using a deterministic rkey
304304-- [Update or delete](../reference/scripts/update-or-delete) — single endpoint handling create, update, and delete
305305-- [Batch save](../reference/scripts/batch-save) — create multiple records in parallel with `Record.save_all()`
306306-- [Sidecar records](../reference/scripts/sidecar-records) — create linked records across collections with a shared rkey
307307-- [Cascading delete](../reference/scripts/cascading-delete) — delete a record and all related records
308308-- [Complex mutations](../reference/scripts/complex-mutations) — load, transform, and save a record with multiple field changes
302302+- [Create a record](../reference/scripts/create-record.md) — simple write that saves input as a record
303303+- [Upsert a record](../reference/scripts/upsert-record.md) — create or update using a deterministic rkey
304304+- [Update or delete](../reference/scripts/update-or-delete.md) — single endpoint handling create, update, and delete
305305+- [Batch save](../reference/scripts/batch-save.md) — create multiple records in parallel with `Record.save_all()`
306306+- [Sidecar records](../reference/scripts/sidecar-records.md) — create linked records across collections with a shared rkey
307307+- [Cascading delete](../reference/scripts/cascading-delete.md) — delete a record and all related records
308308+- [Complex mutations](../reference/scripts/complex-mutations.md) — load, transform, and save a record with multiple field changes
309309310310## Next steps
311311312312-- [Lexicons](lexicons): Understand how record, query, and procedure lexicons work together
313313-- [XRPC API](../reference/xrpc-api): See how endpoints behave with and without Lua scripts
314314-- [Dashboard](../getting-started/dashboard#lua-editor): Use the web editor with context-aware completions
312312+- [Lexicons](lexicons.md): Understand how record, query, and procedure lexicons work together
313313+- [XRPC API](../reference/xrpc-api.md): See how endpoints behave with and without Lua scripts
314314+- [Dashboard](../getting-started/dashboard.md#lua-editor): Use the web editor with context-aware completions
+3-3
docs/reference/admin-api.md
···11# Admin API
2233-The admin API lets you manage lexicons, monitor records, run backfill jobs, and control admin access. All endpoints live under `/admin` and require an [AIP](https://github.com/graze-social/aip)-issued Bearer token from a DID that exists in the `admins` table. You can also manage all of this through the [web dashboard](../getting-started/dashboard).
33+The admin API lets you manage lexicons, monitor records, run backfill jobs, and control admin access. All endpoints live under `/admin` and require an [AIP](https://github.com/graze-social/aip)-issued Bearer token from a DID that exists in the `admins` table. You can also manage all of this through the [web dashboard](../getting-started/dashboard.md).
4455## Auth
66···115115116116## Network Lexicons
117117118118-Network lexicons are fetched from the AT Protocol network via DNS TXT resolution and kept updated via Tap. See [Lexicons - Network lexicons](../guides/lexicons#network-lexicons) for background.
118118+Network lexicons are fetched from the AT Protocol network via DNS TXT resolution and kept updated via Tap. See [Lexicons - Network lexicons](../guides/lexicons.md#network-lexicons) for background.
119119120120### Add a network lexicon
121121···212212213213## Tap Stats
214214215215-Aggregate stats from the [Tap](https://github.com/bluesky-social/indigo/tree/main/cmd/tap) instance. Useful for monitoring backfill progress. See [Backfill - Job lifecycle](../guides/backfill#job-lifecycle) for context.
215215+Aggregate stats from the [Tap](https://github.com/bluesky-social/indigo/tree/main/cmd/tap) instance. Useful for monitoring backfill progress. See [Backfill - Job lifecycle](../guides/backfill.md#job-lifecycle) for context.
216216217217### Get Tap stats
218218
+1-1
docs/reference/architecture.md
···11# Architecture
2233-Guide for contributors working on HappyView itself. For a user-facing overview, see the [Introduction](/).
33+Guide for contributors working on HappyView itself. For a user-facing overview, see the [Introduction](/README.md).
4455## System overview
66
+5-5
docs/reference/glossary.md
···12121313**Handle** — A human-readable name for an account (e.g. `user.bsky.social`). Handles resolve to a DID via DNS or the PLC directory.
14141515-**Lexicon** — A schema definition for AT Protocol data types and API methods. Lexicons define what records look like, what endpoints exist, and what parameters they accept. See [Lexicons](../guides/lexicons).
1515+**Lexicon** — A schema definition for AT Protocol data types and API methods. Lexicons define what records look like, what endpoints exist, and what parameters they accept. See [Lexicons](../guides/lexicons.md).
16161717**NSID** (Namespaced Identifier) — A reverse-DNS identifier for a lexicon (e.g. `xyz.statusphere.status`). The authority is everything except the last segment.
1818···22222323**Record** — A single piece of data in an AT Protocol repository, identified by an AT URI (e.g. `at://did:plc:abc/xyz.statusphere.status/abc123`).
24242525-**Relay** — A network service that aggregates repository data from many PDSes. HappyView queries the relay during [backfill](../guides/backfill) to discover which repos contain records for a given collection, then delegates the actual record fetching to Tap.
2525+**Relay** — A network service that aggregates repository data from many PDSes. HappyView queries the relay during [backfill](../guides/backfill.md) to discover which repos contain records for a given collection, then delegates the actual record fetching to Tap.
26262727**rkey** (Record Key) — The unique key for a record within a collection and repo. These are most commonly TIDs (timestamp-based) or NSIDs.
28282929**TID** (Timestamp Identifier) — A 13-character sortable identifier used as a record key. Generated from the current timestamp.
30303131-**XRPC** — The HTTP-based RPC protocol used by the AT Protocol. Query methods map to GET requests, procedure methods map to POST requests. See [XRPC API](xrpc-api).
3131+**XRPC** — The HTTP-based RPC protocol used by the AT Protocol. Query methods map to GET requests, procedure methods map to POST requests. See [XRPC API](xrpc-api.md).
32323333## HappyView-specific terms
34343535**AIP** — [Authentication and Identity Provider](https://github.com/graze-social/aip). An external service that handles AT Protocol OAuth for HappyView. Issues Bearer tokens used for authentication.
36363737-**Backfill** — The process of bulk-indexing existing records from the network. HappyView discovers repos via the relay and delegates record fetching to Tap. Runs when a new record-type lexicon is uploaded or triggered manually. See [Backfill](../guides/backfill).
3737+**Backfill** — The process of bulk-indexing existing records from the network. HappyView discovers repos via the relay and delegates record fetching to Tap. Runs when a new record-type lexicon is uploaded or triggered manually. See [Backfill](../guides/backfill.md).
38383939-**Network lexicon** — A lexicon fetched directly from the AT Protocol network via DNS authority resolution, rather than uploaded manually. See [Lexicons - Network lexicons](../guides/lexicons#network-lexicons).
3939+**Network lexicon** — A lexicon fetched directly from the AT Protocol network via DNS authority resolution, rather than uploaded manually. See [Lexicons - Network lexicons](../guides/lexicons.md#network-lexicons).
40404141**Tap** — A [firehose consumer and backfill worker](https://github.com/bluesky-social/indigo/tree/main/cmd/tap) that handles real-time record streaming, cryptographic verification, and historical record fetching. HappyView connects to Tap via WebSocket to receive record events, and delegates backfill work to Tap via its HTTP API.
4242
+5-5
docs/reference/production-deployment.md
···11# Deployment
2233-HappyView requires a Postgres database and an [AIP](https://github.com/graze-social/aip) instance for OAuth. The [Quickstart](../getting-started/deployment/railway) covers the fastest path with Railway. This page covers other deployment options.
33+HappyView requires a Postgres database and an [AIP](https://github.com/graze-social/aip) instance for OAuth. The [Quickstart](../getting-started/deployment/railway.md) covers the fastest path with Railway. This page covers other deployment options.
4455## Docker
66···1010docker build -t happyview .
1111```
12121313-For local development, see [Docker deployment](../getting-started/deployment/docker).
1313+For local development, see [Docker deployment](../getting-started/deployment/docker.md).
14141515### Production Compose example
1616···505051511. Provision a Postgres 17+ database
52522. Deploy an [AIP](https://github.com/graze-social/aip) instance (handles OAuth for your AppView)
5353-3. Set `DATABASE_URL` and `AIP_URL` environment variables (see [Configuration](../getting-started/configuration) for all options)
5353+3. Set `DATABASE_URL` and `AIP_URL` environment variables (see [Configuration](../getting-started/configuration.md) for all options)
54544. Deploy the Docker image or build from source
55555. HappyView listens on `PORT` (default `3000`)
56566. Health check: `GET /health` returns `ok`
57575858-For Railway specifically, the [Quickstart](../getting-started/deployment/railway) template handles all of this with a single click.
5858+For Railway specifically, the [Quickstart](../getting-started/deployment/railway.md) template handles all of this with a single click.
59596060## Database
6161···67676868## Logging
69697070-HappyView uses the `RUST_LOG` environment variable to control log output. The default (`happyview=debug,tower_http=debug`) logs all HappyView activity and HTTP requests. For production, consider `happyview=info,tower_http=info` to reduce noise. See [Configuration](../getting-started/configuration) for details.
7070+HappyView uses the `RUST_LOG` environment variable to control log output. The default (`happyview=debug,tower_http=debug`) logs all HappyView activity and HTTP requests. For production, consider `happyview=info,tower_http=info` to reduce noise. See [Configuration](../getting-started/configuration.md) for details.
+2-2
docs/reference/scripts/batch-save.md
···23232424## How it works
25252626-1. Iterate over `input.items` and create a [`Record`](../../guides/scripting#record-api) instance for each item.
2727-2. Call [`Record.save_all()`](../../guides/scripting#static-methods) to save all records in parallel, rather than one at a time.
2626+1. Iterate over `input.items` and create a [`Record`](../../guides/scripting.md#record-api) instance for each item.
2727+2. Call [`Record.save_all()`](../../guides/scripting.md#static-methods) to save all records in parallel, rather than one at a time.
28283. Collect the resulting AT URIs and return them.
29293030## Usage
+1-1
docs/reference/scripts/cascading-delete.md
···494950501. Load the primary record by URI. Return early if it doesn't exist.
51512. Query for related records, in this example comments by the same user that reference the primary record's URI.
5252-3. Load each related record with [`Record.load`](../../guides/scripting#static-methods) to get a deletable `Record` instance.
5252+3. Load each related record with [`Record.load`](../../guides/scripting.md#static-methods) to get a deletable `Record` instance.
53534. Delete everything. Each `r:delete()` removes the record from the user's PDS and the local index.
54545555## Usage
+2-2
docs/reference/scripts/complex-mutations.md
···56565757## How it works
58585959-1. Load the existing record with [`Record.load`](../../guides/scripting#static-methods). This gives you a mutable `Record` instance with all the current field values.
5959+1. Load the existing record with [`Record.load`](../../guides/scripting.md#static-methods). This gives you a mutable `Record` instance with all the current field values.
60602. Apply transformations directly on the record's fields:
6161 - **Increment a counter**: use `or 0` to handle the field being `nil` on first access.
6262 - **Merge tags**: iterate over `input.tags`, skip duplicates already in `r.tags`, append new ones, then trim the list to 10.
6363 - **Normalize a string**: use `string.gsub` to trim whitespace.
6464- - **Set a timestamp**: use [`now()`](../../guides/scripting#utility-globals) for UTC ISO 8601.
6464+ - **Set a timestamp**: use [`now()`](../../guides/scripting.md#utility-globals) for UTC ISO 8601.
65653. Call `r:save()`. Since `_uri` is set (from the load), this calls `putRecord` to update the record on the user's PDS.
66666767## Usage
+1-1
docs/reference/scripts/create-record.md
···14141515## How it works
16161717-1. Create a new [`Record`](../../guides/scripting#record-api) instance from the target collection, populated with the fields from the request body.
1717+1. Create a new [`Record`](../../guides/scripting.md#record-api) instance from the target collection, populated with the fields from the request body.
18182. Call `r:save()`, which creates the record on the caller's PDS and indexes it locally.
19193. Return the AT URI and CID of the newly created record.
2020
+1-1
docs/reference/scripts/expanded-query.md
···51511. Query statuses from the target collection with pagination, same as a normal list query.
52522. Extract the unique DIDs from the returned status URIs using `string.match`.
53533. Build an AT URI for each DID's `app.bsky.actor.profile/self` record (this is where Bluesky profiles live).
5454-4. Load all profiles in parallel with [`Record.load_all`](../../guides/scripting#static-methods). Profiles that aren't indexed locally return `nil` and are skipped.
5454+4. Load all profiles in parallel with [`Record.load_all`](../../guides/scripting.md#static-methods). Profiles that aren't indexed locally return `nil` and are skipped.
55555. Return statuses and profiles as separate keys, with the cursor from the status query.
56565757## Usage
+1-1
docs/reference/scripts/get-record.md
···2222## How it works
232324241. Check that the `uri` query parameter is present. Return a structured error if missing.
2525-2. Look up the record with [`db.get`](../../guides/scripting#dbget), which returns the record table or `nil`.
2525+2. Look up the record with [`db.get`](../../guides/scripting.md#dbget), which returns the record table or `nil`.
26263. Return the record wrapped in an object.
27272828## Usage
+2-2
docs/reference/scripts/list-or-fetch.md
···25252626## How it works
27272828-1. If a `uri` query parameter is provided, fetch that single record with [`db.get`](../../guides/scripting#dbget) and return it. If it doesn't exist, return a structured error (using `error()` would trigger a 500 response).
2929-2. Otherwise, list records from the target collection using [`db.query`](../../guides/scripting#dbquery), with optional filtering by `did` and pagination via `limit`/`offset`. Since query parameters arrive as strings, `tonumber()` converts them to numbers.
2828+1. If a `uri` query parameter is provided, fetch that single record with [`db.get`](../../guides/scripting.md#dbget) and return it. If it doesn't exist, return a structured error (using `error()` would trigger a 500 response).
2929+2. Otherwise, list records from the target collection using [`db.query`](../../guides/scripting.md#dbquery), with optional filtering by `did` and pagination via `limit`/`offset`. Since query parameters arrive as strings, `tonumber()` converts them to numbers.
30303131## Usage
3232
+1-1
docs/reference/scripts/paginated-list.md
···2323## How it works
242425251. Parse `limit` from the query string, defaulting to 20 and capping at 100.
2626-2. Call [`db.query`](../../guides/scripting#dbquery) with the target collection, optional DID filter, and offset-based pagination.
2626+2. Call [`db.query`](../../guides/scripting.md#dbquery) with the target collection, optional DID filter, and offset-based pagination.
27273. Return the result directly. `db.query` returns `{ records = [...], cursor = "..." }` where `cursor` is present when more records exist.
28282929## Usage
+2-2
docs/reference/scripts/sidecar-records.md
···32323333## How it works
34343535-1. Generate a single [`TID()`](../../guides/scripting#utility-globals) to use as the rkey for both records.
3535+1. Generate a single [`TID()`](../../guides/scripting.md#utility-globals) to use as the rkey for both records.
36362. Create a `Record` for each collection and call `r:set_rkey()` with the shared rkey.
3737-3. Save both records in parallel with [`Record.save_all()`](../../guides/scripting#static-methods).
3737+3. Save both records in parallel with [`Record.save_all()`](../../guides/scripting.md#static-methods).
38384. Return both URIs so the client knows the identity of each record.
39394040## Usage
+2-2
docs/reference/scripts/update-or-delete.md
···30303131## How it works
32323333-1. If `input.delete` is truthy and `input.uri` is provided, load the record with [`Record.load`](../../guides/scripting#static-methods) and delete it.
3434-2. If only `input.uri` is provided, load the existing record with [`Record.load`](../../guides/scripting#static-methods), update its fields, and save it back. Since `_uri` is already set, `r:save()` calls `putRecord` instead of `createRecord`.
3333+1. If `input.delete` is truthy and `input.uri` is provided, load the record with [`Record.load`](../../guides/scripting.md#static-methods) and delete it.
3434+2. If only `input.uri` is provided, load the existing record with [`Record.load`](../../guides/scripting.md#static-methods), update its fields, and save it back. Since `_uri` is already set, `r:save()` calls `putRecord` instead of `createRecord`.
35353. If neither condition matches, create a new record from the input.
36363737## Usage
+2-2
docs/reference/scripts/upsert-record.md
···32323333## How it works
34343535-1. Use the client-provided `input.rkey` if present, otherwise generate a new [`TID()`](../../guides/scripting#utility-globals). This means omitting `rkey` always creates, while providing one enables updates.
3636-2. Build the AT URI from the caller's DID, the target collection, and the rkey, then try to load it with [`Record.load`](../../guides/scripting#static-methods).
3535+1. Use the client-provided `input.rkey` if present, otherwise generate a new [`TID()`](../../guides/scripting.md#utility-globals). This means omitting `rkey` always creates, while providing one enables updates.
3636+2. Build the AT URI from the caller's DID, the target collection, and the rkey, then try to load it with [`Record.load`](../../guides/scripting.md#static-methods).
37373. If the record exists, update its fields and save. Since `_uri` is already set, `r:save()` calls `putRecord`.
38384. If it doesn't exist, create a new record, set the rkey explicitly with `r:set_rkey()`, and save. This calls `createRecord` with the specified rkey.
3939
+7-7
docs/reference/troubleshooting.md
···8899**Causes**:
10101111-- The lexicon hasn't been uploaded yet. Check with `GET /admin/lexicons` or the [dashboard](../getting-started/dashboard).
1111+- The lexicon hasn't been uploaded yet. Check with `GET /admin/lexicons` or the [dashboard](../getting-started/dashboard.md).
1212- The lexicon's `defs.main.type` doesn't match the HTTP method. Queries are `GET`, procedures are `POST`.
1313- The NSID in the URL doesn't match the `id` field in the uploaded lexicon JSON.
1414···18181919**Causes**:
20202121-- The query lexicon is missing a `target_collection`. Without it, the query doesn't know which records to read. See [Lexicons - target_collection](../guides/lexicons#target-collection).
2121+- The query lexicon is missing a `target_collection`. Without it, the query doesn't know which records to read. See [Lexicons - target_collection](../guides/lexicons.md#target-collection).
2222- The record-type lexicon hasn't finished backfilling. Check backfill status with `GET /admin/backfill/status` or the dashboard.
2323-- Records exist on the network but HappyView hasn't indexed them yet. Tap only picks up new events from when the collection filter was added. Use [backfill](../guides/backfill) for historical records.
2323+- Records exist on the network but HappyView hasn't indexed them yet. Tap only picks up new events from when the collection filter was added. Use [backfill](../guides/backfill.md) for historical records.
24242525## Procedure returns 401 Unauthorized
2626···515152521. Check the server logs: the full error message is logged at error level but not exposed to the client.
53532. Use `log("message")` in your script to trace execution. Output appears in server logs at debug level (requires `RUST_LOG` to include debug).
5454-3. If you hit the execution limit, your script likely has an infinite loop or is processing too much data. See [Lua Scripting - Sandbox](../guides/scripting#sandbox).
5454+3. If you hit the execution limit, your script likely has an infinite loop or is processing too much data. See [Lua Scripting - Sandbox](../guides/scripting.md#sandbox).
55555656-See [Lua Scripting - Debugging](../guides/scripting#debugging) for more.
5656+See [Lua Scripting - Debugging](../guides/scripting.md#debugging) for more.
57575858## Backfill job stuck in "pending" or "running"
5959···6565- The relay (`RELAY_URL`) may be unreachable or slow to respond. Check connectivity.
6666- Individual PDS fetches can fail silently. The worker logs warnings and continues. Check server logs for details.
67676868-See [Backfill](../guides/backfill) for how the process works.
6868+See [Backfill](../guides/backfill.md) for how the process works.
69697070## Records not appearing in real time
7171···9797- The database user doesn't have sufficient permissions. HappyView needs to create tables (migrations run automatically on startup).
9898- Postgres version is too old. HappyView requires Postgres 17+.
9999100100-See [Configuration](../getting-started/configuration) for environment variable details.
100100+See [Configuration](../getting-started/configuration.md) for environment variable details.
+8-8
docs/reference/xrpc-api.md
···11# XRPC API
2233-[XRPC](https://atproto.com/specs/xrpc) is the HTTP-based RPC protocol used by the AT Protocol. HappyView dynamically registers XRPC endpoints based on your uploaded [lexicons](../guides/lexicons): query lexicons become `GET /xrpc/{nsid}` routes, procedure lexicons become `POST /xrpc/{nsid}` routes.
33+[XRPC](https://atproto.com/specs/xrpc) is the HTTP-based RPC protocol used by the AT Protocol. HappyView dynamically registers XRPC endpoints based on your uploaded [lexicons](../guides/lexicons.md): query lexicons become `GET /xrpc/{nsid}` routes, procedure lexicons become `POST /xrpc/{nsid}` routes.
4455-If a query or procedure lexicon has a [Lua script](../guides/scripting) attached, the script handles the request. Otherwise, HappyView uses built-in default behavior (described below).
55+If a query or procedure lexicon has a [Lua script](../guides/scripting.md) attached, the script handles the request. Otherwise, HappyView uses built-in default behavior (described below).
6677## Auth
88···71717272## Dynamic query endpoints
73737474-Query endpoints are generated from lexicons with `type: "query"`. Without a [Lua script](../guides/scripting), they support two built-in modes depending on whether a `uri` parameter is provided.
7474+Query endpoints are generated from lexicons with `type: "query"`. Without a [Lua script](../guides/scripting.md), they support two built-in modes depending on whether a `uri` parameter is provided.
75757676### Single record
7777···133133134134## Dynamic procedure endpoints
135135136136-Procedure endpoints are generated from lexicons with `type: "procedure"`. Without a [Lua script](../guides/scripting), HappyView auto-detects create vs update based on whether the request body contains a `uri` field.
136136+Procedure endpoints are generated from lexicons with `type: "procedure"`. Without a [Lua script](../guides/scripting.md), HappyView auto-detects create vs update based on whether the request body contains a `uri` field.
137137138138### Create a record
139139···195195- `{"error": "script execution failed"}`: syntax error, runtime error, or missing `handle()` function
196196- `{"error": "script exceeded execution time limit"}`: the script hit the 1,000,000 instruction limit
197197198198-The full error details are logged server-side but not exposed to the client. See [Lua Scripting - Debugging](../guides/scripting#debugging) for how to diagnose script issues.
198198+The full error details are logged server-side but not exposed to the client. See [Lua Scripting - Debugging](../guides/scripting.md#debugging) for how to diagnose script issues.
199199200200### PDS errors
201201···203203204204## Next steps
205205206206-- [Lua Scripting](../guides/scripting): Override the default query and procedure behavior with custom logic
207207-- [Lexicons](../guides/lexicons): Understand how lexicons generate these endpoints
208208-- [Admin API](admin-api): Manage lexicons and monitor your instance
206206+- [Lua Scripting](../guides/scripting.md): Override the default query and procedure behavior with custom logic
207207+- [Lexicons](../guides/lexicons.md): Understand how lexicons generate these endpoints
208208+- [Admin API](admin-api.md): Manage lexicons and monitor your instance
+9-9
docs/tutorials/statusphere.md
···55In this tutorial, you'll set up HappyView to act as the AppView for Statusphere. By the end, you'll have automatically indexed records and automatically generated XPRC endpoints.
6677:::tip
88-This tutorial assumes you have a running HappyView instance. If you don't, start with the [Quickstart](../getting-started/deployment/railway) or one of the local development guides ([Docker](../getting-started/deployment/docker), [from source](../getting-started/deployment/other)).
88+This tutorial assumes you have a running HappyView instance. If you don't, start with the [Quickstart](../getting-started/deployment/railway.md) or one of the local development guides ([Docker](../getting-started/deployment/docker.md), [from source](../getting-started/deployment/other.md)).
99:::
10101111## The Statusphere lexicon
···23232424First, upload the `xyz.statusphere.status` lexicon to HappyView. This tells HappyView to start indexing Statusphere records from across the network as they're created, updated, or deleted.
25252626-The examples below use `$TOKEN` as a placeholder for an AIP-issued access token. See [Authentication](../getting-started/authentication) for how to get one.
2626+The examples below use `$TOKEN` as a placeholder for an AIP-issued access token. See [Authentication](../getting-started/authentication.md) for how to get one.
27272828```sh
2929curl -X POST http://localhost:3000/admin/lexicons \
···5252 }'
5353```
54545555-HappyView now subscribes to `xyz.statusphere.status` via Tap. The `backfill` flag tells HappyView to also index existing status records from the network. You can monitor progress with `GET /admin/backfill/status` or the [dashboard](../getting-started/dashboard).
5555+HappyView now subscribes to `xyz.statusphere.status` via Tap. The `backfill` flag tells HappyView to also index existing status records from the network. You can monitor progress with `GET /admin/backfill/status` or the [dashboard](../getting-started/dashboard.md).
56565757:::tip
5858-Since the `xyz.statusphere.status` lexicon is [published on the AT Protocol network](../guides/lexicons#network-lexicons), you can also add it as a network lexicon instead of uploading the JSON manually:
5858+Since the `xyz.statusphere.status` lexicon is [published on the AT Protocol network](../guides/lexicons.md#network-lexicons), you can also add it as a network lexicon instead of uploading the JSON manually:
59596060```sh
6161curl -X POST http://localhost:3000/admin/network-lexicons \
···129129}
130130```
131131132132-See [XRPC API](../reference/xrpc-api) for the full default query behavior.
132132+See [XRPC API](../reference/xrpc-api.md) for the full default query behavior.
133133134134## Step 4: Enhance the query with a Lua script
135135136136-The default query behavior works, but let's customize it with a [Lua script](../guides/scripting). Here's a script that handles single-record lookups by URI and paginated listing with an optional DID filter:
136136+The default query behavior works, but let's customize it with a [Lua script](../guides/scripting.md). Here's a script that handles single-record lookups by URI and paginated listing with an optional DID filter:
137137138138```lua
139139function handle()
···298298299299## Next steps
300300301301-- [Lua Scripting](../guides/scripting): Explore the full Record and database APIs to build more complex queries
302302-- [Lexicons](../guides/lexicons): Learn about network lexicons, the backfill flag, and target collections
303303-- [XRPC API](../reference/xrpc-api): Understand how the generated endpoints behave
301301+- [Lua Scripting](../guides/scripting.md): Explore the full Record and database APIs to build more complex queries
302302+- [Lexicons](../guides/lexicons.md): Learn about network lexicons, the backfill flag, and target collections
303303+- [XRPC API](../reference/xrpc-api.md): Understand how the generated endpoints behave
304304- [Statusphere example app](https://github.com/bluesky-social/statusphere-example-app): See the full Statusphere frontend
305305- [ATProto Statusphere guide](https://atproto.com/guides/applications): Deep dive into how the app works at the protocol level