Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Reference

Configuration

Set via environment (a .env file is loaded in development).

VariableRequiredDefaultMeaning
LABELER_DIDyesThe labeler’s DID (did:web:... or did:plc:...).
LABELER_SIGNING_KEYyes64-char hex secp256k1 private key. From npm run keygen.
ADMIN_TOKENyesBearer token for POST /admin/labels.
DATABASE_URLyesPostgres connection string.
PORTno4100HTTP port.
REPORT_FORWARD_URLnoBackend endpoint to forward inbound reports to.
REPORT_FORWARD_TOKENnoBearer token sent with forwarded reports.
REPORT_ENRICHnotrueResolve and attach reported content to forwarded reports.
REPORT_FORWARD_FORMATnorawraw (Bearer JSON) or coop (Coop /api/v1/report + X-API-KEY).
COOP_POST_TYPEnoATproto-postCoop item type id for posts (coop format).
COOP_ACCOUNT_TYPEnoATproto-accountCoop item type id for accounts (coop format).
PDS_URLnoYour PDS, for enforcement. Enables POST /admin/enforce.
PDS_ADMIN_PASSWORDnoAdmin password for PDS_URL.

Ingestion worker (npm run ingest) only:

VariableRequiredDefaultMeaning
COOP_ITEMS_URLyesCoop’s /api/v1/items/async endpoint.
COOP_ITEMS_API_KEYyesCoop org API key, sent as X-API-KEY.
JETSTREAM_URLnowss://jetstream2.us-east.bsky.network/subscribeJetstream instance.
JETSTREAM_COLLECTIONSnoapp.bsky.feed.post,app.bsky.actor.profileRecord types to stream.
JETSTREAM_WANTED_DIDSno(all)Restrict to specific accounts.
INGEST_BATCH_SIZEno50Max items per POST.
INGEST_BATCH_INTERVAL_MSno1000Flush interval.

See Ingesting content into Coop.

Endpoints

MethodPathAuthPurpose
GET/healthnoneLiveness. Returns the labeler DID.
POST/admin/labelsbearerCreate and negate labels.
POST/admin/enforcebearerTake down or restore on your PDS.
POST/coop/actionbearerCoop CUSTOM_ACTION shape: label and/or take down.
POST/labelbearerAlias of /coop/action, matching the workshop relay path.
GET/xrpc/com.atproto.label.queryLabelsnoneQuery labels by URI pattern.
GET (WS)/xrpc/com.atproto.label.subscribeLabelsnoneLabel firehose.
POST/xrpc/com.atproto.moderation.createReportservice JWTAccept a report.

POST /admin/enforce

Take down or restore a record or account on your own PDS. Bearer-authenticated with ADMIN_TOKEN. Body: subject (a record { uri, cid } or account { did }), takedown (true to take down, false to restore, default true), and optional ref. Requires PDS_URL and PDS_ADMIN_PASSWORD; returns 501 otherwise. See Enforcement.

POST /coop/action

Accepts Coop’s CUSTOM_ACTION webhook body directly, so a Coop action uses troposphere with no adapter. Bearer-authenticated with ADMIN_TOKEN. Body: item.id (the subject atproto URI or DID) and custom with any of create / negate (label values) and takedown (boolean), plus optional cid and ref. custom.labelVal is accepted as an alias for create, so a Coop action wired for the old workshop relay (posting { item, custom: { labelVal } } to /label) works unchanged. For a record without a cid, the current version is resolved from its own PDS; if that cannot be resolved the label is still emitted, without a cid. See Deploying with Coop.

POST /xrpc/com.atproto.moderation.createReport

Standard atproto report intake, reached by PDS proxying (not called directly). Auth is the inter-service JWT the reporter’s PDS attaches. Input is reasonType, optional reason, and a subject (a com.atproto.admin.defs#repoRef for an account or a com.atproto.repo.strongRef for a record). Returns the created report with an integer id and the reportedBy DID. See Receiving reports.

POST /admin/labels

Request:

{
  "subject": { "uri": "at://... or did:...", "cid": "optional" },
  "create": ["label-value"],
  "negate": ["label-value"]
}

Response:

{ "labels": [ { "seq": 1, "val": "label-value", "neg": false } ] }

Errors: 401 (missing or wrong token), 400 (missing subject.uri, or neither create nor negate provided).

Scripts

CommandWhat it does
npm run keygenGenerate a signing key and print the public multibase.
npm run db:initApply the Postgres schema.
npm run ingestStream Jetstream into Coop’s item intake.
npm run devRun with watch (type-stripped, no build step).
npm run buildCompile TypeScript to dist/.
npm startRun the compiled build.
npm run typecheckType-check without emitting.
npm run smokeEnd-to-end check against a running instance.

Database

Two tables. label: key columns seq (bigserial, the firehose cursor), src, uri, cid, val, neg, cts, exp, sig. Timestamps are stored as text so a stored label reproduces exactly the bytes that were signed. report: id (bigserial), reason_type, reason, subject_type, subject_did, subject_uri, subject_cid, reported_by, created_at. Enforcement is not stored; it is applied directly to the PDS.

Building the docs

This book is built with mdBook:

mdbook serve docs      # live preview
mdbook build docs      # output to docs/book