Skip to content

Overview

The admin API lets you manage lexicons, monitor records, run backfill jobs, and control user access. All endpoints live under /admin and require authentication from a DID that exists in the happyview_users table, with the appropriate permissions for the endpoint being called. You can also manage all of this through the web dashboard.

The admin API supports four authentication methods:

  1. API keys — read/write tokens starting with hv_, passed as Authorization: Bearer hv_.... See the API Keys guide for details.
  2. Service auth JWT — atproto inter-service authentication via signed JWTs. The token must be bound to the method dev.happyview.admin.manage (its lxm claim). A PDS mints such a token for an OAuth app only when the user granted it that method, which is what the next method’s permission set does.
  3. Cookie-based session auth — signed session cookies set during the dashboard OAuth login flow. The web dashboard uses this method.
  4. App sessions granted dev.happyview.adminFull — an API client’s DPoP session, when the user approved the permission set dev.happyview.adminFull at sign-in. The app asks for it with the scope include:dev.happyview.adminFull. The session then acts with the user’s own permissions.

HappyView decides whether a session was granted the set by what the user’s authorization server says, never by the scope the app registered. On the session’s first admin request it refreshes the session itself, after checking that the user’s PDS names the session’s authorization server, and reads the granted scope from the token response. It reads it again once that access token expires, so revoking the grant at the PDS ends admin access within one token lifetime. Any other API client session is refused with 403 Forbidden, because those sessions belong to apps built on the instance, and an admin signing in to one must not hand it the instance.

hpy, the HappyView CLI, signs in this way through a client every instance has built in: hvc_hpy, which GET /config publishes as cli_client_key. An operator who wants another app to manage the instance registers it with include:dev.happyview.adminFull among its scopes.

In all cases the resolved DID is checked against the happyview_users table, and the user’s permissions are loaded to authorize the request.

Auto-bootstrap: If the happyview_users table is empty, the first authenticated request automatically creates the caller as the super user with all permissions granted.

Non-user DIDs receive a 403 Forbidden response. Users without the required permission for a specific endpoint also receive 403 Forbidden.

All error responses return JSON with an error field:

{
"error": "description of what went wrong"
}
StatusMeaning
400 Bad RequestInvalid input (missing required fields, malformed lexicon JSON)
401 UnauthorizedMissing or invalid API key or service auth JWT
403 ForbiddenAuthenticated DID is not in the users table, or user lacks the required permission
404 Not FoundLexicon, user, or backfill job not found
Terminal window
# All examples assume $TOKEN is an API key (hv_...)
AUTH="Authorization: Bearer $TOKEN"
GroupDescription
LexiconsUpload, list, get, and delete lexicons and network lexicons
Lexicon PublishingPublish lexicons to the network and manage publishing rules
StatsRecord counts by collection, admin and client counts
BackfillCreate and monitor historical backfill jobs
Event LogsQuery the audit trail of system events
API KeysCreate, list, and revoke API keys
UsersCreate, list, update, and delete operators
LabelersManage external labeler subscriptions
RecordsList and delete indexed records
SpacesBrowse spaces and their records for moderation
Instance SettingsConfigure app name, logo, policy URLs, and concurrency settings
DomainsManage domains and their OAuth client identities
OAuth KeysRotate and revoke the instance’s OAuth client-assertion signing key
ScriptsCreate, list, update, and delete Lua scripts
Script VariablesEncrypted key/value pairs for Lua scripts
API ClientsRegister and manage third-party XRPC clients
PluginsInstall, configure, and manage plugins
Dead LettersList, inspect, dismiss, retry, and reindex dead-lettered events
Service IdentityGet and update service identity configuration
Service EntriesManage service entries and their XRPC bindings
Verification MethodsCreate, list, and delete DID verification methods
Feature FlagsList feature flag status
PermissionsList available permission definitions

Each admin API endpoint requires a specific permission. See the Permissions guide for the full list of permissions and templates.

EndpointRequired Permission
POST /admin/lexiconslexicons:create
GET /admin/lexiconslexicons:read
GET /admin/lexicons/{id}lexicons:read
DELETE /admin/lexicons/{id}lexicons:delete
POST /admin/network-lexiconslexicons:create
GET /admin/network-lexiconslexicons:read
DELETE /admin/network-lexicons/{id}lexicons:delete
GET /admin/lexicon-publishinglexicons:read
GET /admin/lexicon-publishing/dnslexicons:read
POST /admin/lexicon-publishing/ruleslexicons:publish
PATCH /admin/lexicon-publishing/rules/{id}lexicons:publish
DELETE /admin/lexicon-publishing/rules/{id}lexicons:publish
POST /admin/lexicon-publishing/publishlexicons:publish
POST /admin/lexicon-publishing/unpublishlexicons:publish
GET /admin/statsstats:read
GET /admin/stats/countsstats:read
GET /admin/jetstream/statusstats:read
POST /admin/backfillbackfill:create
GET /admin/backfill/statusbackfill:read
POST /admin/backfill/{id}/cancelbackfill:create
POST /admin/backfill/{id}/pausebackfill:create
POST /admin/backfill/{id}/resumebackfill:create
GET /admin/backfill/{id}/eventsbackfill:read
GET /admin/backfill/{id}/reposbackfill:read
GET /admin/backfill/{id}/pds-summarybackfill:read
DELETE /admin/backfill/{id}/detailsbackfill:create
DELETE /admin/backfill/detailsbackfill:create
GET /admin/eventsevents:read
POST /admin/api-keysapi-keys:create
GET /admin/api-keysapi-keys:read
DELETE /admin/api-keys/{id}api-keys:delete
POST /admin/usersusers:create
GET /admin/usersusers:read
GET /admin/users/{id}users:read
PATCH /admin/users/{id}/permissionsusers:update
DELETE /admin/users/{id}users:delete
POST /admin/users/transfer-superSuperadmin only
GET /admin/script-variablesscript-variables:read
POST /admin/script-variablesscript-variables:create
DELETE /admin/script-variables/{key}script-variables:delete
GET /admin/scriptsscripts:read
POST /admin/scriptsscripts:manage
GET /admin/scripts/languagesscripts:read
GET /admin/scripts/{id}scripts:read
PATCH /admin/scripts/{id}scripts:manage
DELETE /admin/scripts/{id}scripts:manage
POST /admin/labelerslabelers:create
GET /admin/labelerslabelers:read
PATCH /admin/labelers/{did}labelers:create
DELETE /admin/labelers/{did}labelers:delete
GET /admin/recordsrecords:read
GET /admin/records/collectionsrecords:read
DELETE /admin/recordsrecords:delete
DELETE /admin/records/collectionrecords:delete-collection
GET /admin/spacesspaces:read
GET /admin/spaces/{id}spaces:read
GET /admin/spaces/{id}/recordsspaces:manage-records
GET /admin/spaces/{id}/blobspaces:manage-records
GET /admin/database/statussettings:manage
POST /admin/database/vacuum/schedulesettings:manage
DELETE /admin/database/vacuum/schedulesettings:manage
POST /admin/storage/retrainsettings:manage
GET /admin/settingssettings:manage
GET /admin/settings/db-infosettings:manage
PUT /admin/settings/{key}settings:manage
DELETE /admin/settings/{key}settings:manage
PUT /admin/settings/logosettings:manage
DELETE /admin/settings/logosettings:manage
GET /admin/pluginsplugins:read
POST /admin/pluginsplugins:create
POST /admin/plugins/previewplugins:read
GET /admin/plugins/officialplugins:read
DELETE /admin/plugins/{id}plugins:delete
POST /admin/plugins/{id}/reloadplugins:create
POST /admin/plugins/{id}/check-updateplugins:read
GET /admin/plugins/{id}/secretsplugins:read
PUT /admin/plugins/{id}/secretsplugins:create
GET /admin/domainssettings:manage
POST /admin/domainssettings:manage
DELETE /admin/domains/{id}settings:manage
POST /admin/domains/{id}/primarysettings:manage
GET /admin/oauth/instance-keysettings:manage
POST /admin/oauth/instance-key/rotatesettings:manage
DELETE /admin/oauth/instance-key/{kid}settings:manage
GET /admin/api-clientsapi-clients:view
POST /admin/api-clientsapi-clients:create
GET /admin/api-clients/{id}api-clients:view
PUT /admin/api-clients/{id}api-clients:edit
DELETE /admin/api-clients/{id}api-clients:delete
POST /admin/api-clients/{id}/auth-keyapi-clients:edit
GET /admin/api-clients/{id}/auth-keyapi-clients:view
GET /admin/api-clients/{id}/auth-keysapi-clients:view
POST /admin/api-clients/{id}/auth-key/recheckapi-clients:edit
POST /admin/api-clients/{id}/auth-key/rotateapi-clients:edit
DELETE /admin/api-clients/{id}/auth-key/{kid}api-clients:edit
DELETE /admin/api-clients/{id}/auth-keysapi-clients:edit
GET /admin/dead-lettersdead-letters:read
GET /admin/dead-letters/countdead-letters:read
GET /admin/dead-letters/{id}dead-letters:read
POST /admin/dead-letters/{id}/dismissdead-letters:manage
POST /admin/dead-letters/{id}/retrydead-letters:manage
POST /admin/dead-letters/{id}/reindexdead-letters:manage
POST /admin/dead-letters/bulk/dismissdead-letters:manage
POST /admin/dead-letters/bulk/retrydead-letters:manage
POST /admin/dead-letters/bulk/reindexdead-letters:manage
GET /admin/service-identitysettings:manage
PUT /admin/service-identitysettings:manage
GET /admin/service-entriessettings:manage
POST /admin/service-entriessettings:manage
PUT /admin/service-entries/{id}settings:manage
DELETE /admin/service-entries/{id}settings:manage
GET /admin/service-entries/{id}/xrpcssettings:manage
POST /admin/service-entries/{id}/xrpcssettings:manage
DELETE /admin/service-entries/{id}/xrpcssettings:manage
POST /admin/service-entries/sync-plcsettings:manage
POST /admin/service-entries/sync-plc/requestsettings:manage
POST /admin/service-entries/sync-plc/submitsettings:manage
GET /admin/lexicons/{id}/servicessettings:manage
GET /admin/verification-methodssettings:manage
POST /admin/verification-methodssettings:manage
DELETE /admin/verification-methods/{fragment_id}settings:manage
GET /admin/feature-flagssettings:manage
GET /admin/network-lexicons/resolve/{nsid}lexicons:read
GET /admin/permissionsusers:read
GET /admin/settings/xrpc-proxysettings:manage
PUT /admin/settings/xrpc-proxysettings:manage

GET /admin/jetstream/status reports the live Jetstream consumer since the process started. A platform API key can read it. Unknown values are an explicit null: protocol, host, dictionary_id, applied_seq, applied_time_us and lag_ms are all null before the first connection or event, and the two worker arrays are empty until the worker pool starts.

FieldMeaning
protocolv2 (subscribeEvents) or v1 (/subscribe); null before the first connection
hostThe Jetstream host from JETSTREAM_URL
connectedWhether a subscription is open now
compressionWhether frames arrive zstd-compressed
dictionary_idThe zstd dictionary in use, or null
applied_seqThe newest event that it and every event before it have been applied: the position saved for a restart. null on v1, which has no seq
applied_time_usWhen Jetstream witnessed that event, in microseconds since the Unix epoch
lag_msMilliseconds from applied_time_us to now
worker_queue_depthsEvents waiting in each worker’s queue, one entry per JETSTREAM_WORKERS. The event a worker is applying or retrying is not counted
now_msServer time when the snapshot was taken, in milliseconds since the Unix epoch. Subtract worker_busy_since_ms from it rather than using your own clock
worker_busy_since_msOne entry per worker: when its current event started, in milliseconds since the Unix epoch, or null when idle. An old value with an empty queue means the worker is stuck retrying

GET /admin/database/status reports the database and write-ahead log size, free disk space, and the status of the one-time SQLite vacuum described in Configuration. It also reports the periodic SQLite WAL checkpoint: checkpoint_interval_secs is the SQLITE_CHECKPOINT_INTERVAL_SECS in effect (0 when disabled), wal_checkpoint_bytes is the SQLITE_WAL_CHECKPOINT_BYTES in effect (0 when disabled, and always on Postgres), and checkpoint is the latest run since the process started, or null before the first one and always on Postgres. A run has at (RFC 3339), busy (true when a reader or writer kept it from finishing), wal_frames (frames in the WAL when it ran) and checkpointed_frames (frames copied back into the database file). POST /admin/database/vacuum/schedule arms the vacuum to run at the next restart; DELETE /admin/database/vacuum/schedule disarms it. These only apply to SQLite — on a Postgres-backed instance, scheduling returns 400 Bad Request rather than silently doing nothing.

POST /admin/storage/retrain trains a new zstd dictionary for one collection from up to 20,000 of its most recently stored records, as the background task does when a collection first reaches ZSTD_DICT_TRAIN_AT records (see the compact record format). Records written from then on are compressed with it. Records already compressed keep the dictionary they were compressed with, and records the background task has not reached yet get the new one. One retrain runs at a time. SQLite only.

Terminal window
curl -X POST "http://127.0.0.1:3000/admin/storage/retrain" -H "$AUTH" \
-H 'content-type: application/json' -d '{"collection": "app.bsky.feed.post"}'
{ "collection": "app.bsky.feed.post", "dict_id": 3 }
StatusWhen
200 OKThe dictionary was trained and is in use
400 Bad RequestThe instance runs on Postgres, or RECORD_COMPRESSION=none
404 Not FoundThe instance holds no records in that collection
409 ConflictAnother retrain is still running on this instance; try again when it finishes
422 Unprocessable Entityzstd could not train a dictionary from the records (TrainingFailed)