A standalone AT Protocol labeler: a self-hostable, backend-agnostic alternative to Ozone designed to work with Coop
troposphere docs src reference.md
5.8 kB · 125 lines
Markdown
at main

Reference #

Configuration #

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

Variable Required Default Meaning
LABELER_DID yes The labeler's DID (did:web:... or did:plc:...).
LABELER_SIGNING_KEY yes 64-char hex secp256k1 private key. From npm run keygen.
ADMIN_TOKEN yes Bearer token for POST /admin/labels.
DATABASE_URL yes Postgres connection string.
PORT no 4100 HTTP port.
REPORT_FORWARD_URL no Backend endpoint to forward inbound reports to.
REPORT_FORWARD_TOKEN no Bearer token sent with forwarded reports.
REPORT_ENRICH no true Resolve and attach reported content to forwarded reports.
REPORT_FORWARD_FORMAT no raw raw (Bearer JSON) or coop (Coop /api/v1/report + X-API-KEY).
COOP_POST_TYPE no ATproto-post Coop item type id for posts (coop format).
COOP_ACCOUNT_TYPE no ATproto-account Coop item type id for accounts (coop format).
PDS_URL no Your PDS, for enforcement. Enables POST /admin/enforce.
PDS_ADMIN_PASSWORD no Admin password for PDS_URL.

Ingestion worker (npm run ingest) only:

Variable Required Default Meaning
COOP_ITEMS_URL yes Coop's /api/v1/items/async endpoint.
COOP_ITEMS_API_KEY yes Coop org API key, sent as X-API-KEY.
JETSTREAM_URL no wss://jetstream2.us-east.bsky.network/subscribe Jetstream instance.
JETSTREAM_COLLECTIONS no app.bsky.feed.post,app.bsky.actor.profile Record types to stream.
JETSTREAM_WANTED_DIDS no (all) Restrict to specific accounts.
INGEST_BATCH_SIZE no 50 Max items per POST.
INGEST_BATCH_INTERVAL_MS no 1000 Flush interval.

See Ingesting content into Coop.

Endpoints #

Method Path Auth Purpose
GET /health none Liveness. Returns the labeler DID.
POST /admin/labels bearer Create and negate labels.
POST /admin/enforce bearer Take down or restore on your PDS.
POST /coop/action bearer Coop CUSTOM_ACTION shape: label and/or take down.
POST /label bearer Alias of /coop/action, matching the workshop relay path.
GET /xrpc/com.atproto.label.queryLabels none Query labels by URI pattern.
GET (WS) /xrpc/com.atproto.label.subscribeLabels none Label firehose.
POST /xrpc/com.atproto.moderation.createReport service JWT Accept 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 #

Command What it does
npm run keygen Generate a signing key and print the public multibase.
npm run db:init Apply the Postgres schema.
npm run ingest Stream Jetstream into Coop's item intake.
npm run dev Run with watch (type-stripped, no build step).
npm run build Compile TypeScript to dist/.
npm start Run the compiled build.
npm run typecheck Type-check without emitting.
npm run smoke End-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