Building on the AT Protocol
This guide is for someone building their own thing on the AT Protocol: your own record types
(lexicons), maybe your own PDS, maybe your own AppView. It explains where troposphere fits and,
just as importantly, where it does not assume you are Bluesky.
The short version: troposphere is lexicon-, PDS-, and AppView-agnostic. It handles labeling,
reporting, and enforcement for your stack, whatever that stack is.
What is generic
Your own record types. A label points at an AT-URI (at://did/<collection>/<rkey>) or a
bare DID. Nothing in troposphere inspects the collection, so labeling a com.yourapp.recipe or
a site.standard.document works exactly like labeling an app.bsky.feed.post. The label
values are your own vocabulary too. You are not limited to Bluesky’s record types or label set.
Your own PDS. Report intake resolves the reporter through their DID (did:plc or did:web),
so a report proxied from any PDS authenticates. Enrichment reads the reported record from the
subject’s own PDS. Enforcement takes content down on your PDS. None of these paths call a
Bluesky AppView.
Your own AppView. Your AppView subscribes to your labeler by its DID over the standard
subscribeLabels firehose, the same way Bluesky’s AppView would. queryLabels is the same
standard endpoint. troposphere does not care who is consuming.
What you provide
- A labeler identity. A DID whose document advertises your signing key and this server. Use
did:webunder your own domain for full self-hosting, ordid:plcfor portability. See Publishing your labeler identity. - A moderation workflow, in Coop. Coop is where you build it: review queues, automated enforcement rules, matching banks, reviewer roles. Coop decides what to label or take down and calls troposphere. (Not using Coop? Any tooling that makes an authenticated HTTP request can call the endpoints instead. See Using it from a moderation backend.)
- Label handling in your AppView.
troposphereemits standard labels; your AppView has to read them and decide what to do (hide, warn, badge). That interpretation is your app’s job, as it is for any labeler.
What troposphere does not do
- It does not interpret labels for you. Emitting a
spamlabel does nothing on its own; your AppView (or Bluesky’s, if you publish to that ecosystem) decides what a label means in the UI. - It does not define custom moderation lexicons. It implements the standard
com.atproto.moderation.createReport. If you invent your own report record type, you would handle that separately. Using the standard report procedure keeps you interoperable with existing clients. - It does not resolve enforcement across many PDSes. Enforcement targets one configured PDS (yours). Multi-PDS targeting is on the roadmap.
A walkthrough for a custom stack
Say you run yourapp.example with a lexicon com.yourapp.post, your own PDS, and your own
AppView.
- Create a labeler identity at
did:web:labeler.yourapp.exampleand publish its DID document andapp.bsky.labeler.servicedeclaration with your label values. See Publishing your labeler identity. - Deploy
tropospherewith that DID and a signing key, a Postgres, and (for enforcement)PDS_URL/PDS_ADMIN_PASSWORDpointing at your PDS. See Getting started. - Point it at your backend. Set
REPORT_FORWARD_URLto your backend so inbound reports arrive there. Have your backend callPOST /admin/labelsto publish a label andPOST /admin/enforceto take content down. - Label your own records. A decision to label
at://did:.../com.yourapp.post/abcis one call; the collection beingcom.yourapp.postchanges nothing. - Consume labels in your AppView. Subscribe to
did:web:labeler.yourapp.exampleoversubscribeLabels, hydrate labels onto yourcom.yourapp.postviews, and render them however your product wants.
At no point in this does Bluesky’s infrastructure sit in the path. troposphere is the moderation
labeler for your atmosphere.