Ingesting content into Coop
For Coop to moderate AT Protocol content, that content has to arrive as Coop items. troposphere’s ingestion worker streams Jetstream and posts records to Coop’s item intake over HTTP, so Coop can run rules and route content to review queues. This is the proactive-detection path; user reports arrive separately (see Receiving reports).
Coop itself never connects to Jetstream and needs no atproto-specific connector. This worker does
that job and posts plain items to /items/async, which is why Coop stays generic. If your Coop
shows no atproto content, it is not because Coop lacks a firehose; it is because this worker is not
running or is scoped to different accounts.
Ingestion runs as its own process, opt-in, so a troposphere deployment that only labels does not pay for it.
Running it
COOP_ITEMS_URL=https://your-coop/api/v1/items/async \
COOP_ITEMS_API_KEY=<coop org API key> \
npm run ingest
The worker connects to Jetstream, maps each record to a Coop item, batches, and POSTs
{ items: [...] } to /api/v1/items/async with X-API-KEY. It reconnects with backoff and
maintains a live tail.
Configuration
| Variable | Default | Meaning |
|---|---|---|
COOP_ITEMS_URL | (required) | Coop’s /api/v1/items/async endpoint. |
COOP_ITEMS_API_KEY | (required) | Coop org API key, sent as X-API-KEY. |
JETSTREAM_URL | wss://jetstream2.us-east.bsky.network/subscribe | Jetstream instance. |
JETSTREAM_COLLECTIONS | app.bsky.feed.post,app.bsky.actor.profile | Record types to stream. |
JETSTREAM_WANTED_DIDS | (all) | Restrict to specific accounts. |
INGEST_BATCH_SIZE | 50 | Max items per POST. |
INGEST_BATCH_INTERVAL_MS | 1000 | Flush interval. |
COOP_POST_TYPE / COOP_ACCOUNT_TYPE | ATproto-post / ATproto-account | Coop item type ids. |
Scope your ingestion
The full firehose is enormous. Ingesting every post on the network into Coop is almost never what you want. Scope it:
JETSTREAM_WANTED_DIDSrestricts to your project’s accounts.JETSTREAM_COLLECTIONSrestricts to the record types you moderate.
For a project running its own PDS or AppView, point JETSTREAM_URL at a Jetstream fed by your
own infrastructure, or filter to your accounts.
Item types and custom lexicons
The worker maps app.bsky.feed.post to your post item type and app.bsky.actor.profile to your
account item type. Those item types must already exist in your Coop org (created there once), and
COOP_POST_TYPE / COOP_ACCOUNT_TYPE must be set to their item type IDs (the id Coop
assigns each type, not the display name). Coop’s HTTP intake matches typeId against the id and
rejects the batch otherwise. The mapping produces the same item shape a reported post gets, so
ingested and reported content look identical to Coop.
Mapping additional or custom record types (your own lexicons) to Coop item types is not built in yet; today the worker covers posts and profiles. See the roadmap.
What it does not do
Ingestion only submits items. Whether an item reaches a reviewer is up to Coop’s rules and routing (unlike reports, which Coop always enqueues). This is intentional: the item path is for proactive detection and automated enforcement, so you decide in Coop which content warrants review.