A community based topic aggregation platform built on atproto
1# Aggregator Setup Guide
2
3This guide explains how to set up and register an aggregator with Coves instances.
4
5## Table of Contents
6
7- [Overview](#overview)
8- [Architecture](#architecture)
9- [Prerequisites](#prerequisites)
10- [Quick Start](#quick-start)
11- [Detailed Setup Steps](#detailed-setup-steps)
12- [Authorization Process](#authorization-process)
13- [Posting to Communities](#posting-to-communities)
14- [Rate Limits](#rate-limits)
15- [Security Best Practices](#security-best-practices)
16- [Troubleshooting](#troubleshooting)
17- [API Reference](#api-reference)
18
19## Overview
20
21**Aggregators** are automated services that post content to Coves communities. They are similar to Bluesky's feed generators and labelers - self-managed external services that integrate with the platform.
22
23**Key characteristics**:
24- Self-owned: You create and manage your own PDS account
25- Domain-verified: Prove ownership via `.well-known/atproto-did`
26- Community-authorized: Moderators grant posting permission per-community
27- Rate-limited: 10 posts per hour per community
28
29**Example use cases**:
30- RSS feed aggregators (tech news, blog posts)
31- Social media cross-posters (Twitter → Coves)
32- Event notifications (GitHub releases, weather alerts)
33- Content curation bots (daily links, summaries)
34
35## Architecture
36
37### Data Flow
38
39```
40┌──────────────────────────────────────────────────────────┐
41│ 1. One-Time Setup │
42├──────────────────────────────────────────────────────────┤
43│ Aggregator creates PDS account │
44│ ↓ │
45│ Proves domain ownership (.well-known) │
46│ ↓ │
47│ Registers with Coves (enters users table) │
48│ ↓ │
49│ Writes service declaration │
50│ ↓ │
51│ Jetstream indexes into aggregators table │
52└──────────────────────────────────────────────────────────┘
53
54┌──────────────────────────────────────────────────────────┐
55│ 2. Per-Community Authorization │
56├──────────────────────────────────────────────────────────┤
57│ Moderator writes authorization record │
58│ ↓ │
59│ Jetstream indexes into aggregator_authorizations │
60└──────────────────────────────────────────────────────────┘
61
62┌──────────────────────────────────────────────────────────┐
63│ 3. Posting (Ongoing) │
64├──────────────────────────────────────────────────────────┤
65│ Aggregator calls post creation endpoint │
66│ ↓ │
67│ Handler validates: │
68│ - Author in users table ✓ │
69│ - Author in aggregators table ✓ │
70│ - Authorization exists ✓ │
71│ - Rate limit not exceeded ✓ │
72│ ↓ │
73│ Post written to community's PDS │
74│ ↓ │
75│ Jetstream indexes post │
76└──────────────────────────────────────────────────────────┘
77```
78
79### Database Tables
80
81**users** - All actors (users, communities, aggregators)
82```sql
83CREATE TABLE users (
84 did TEXT PRIMARY KEY,
85 handle TEXT NOT NULL,
86 pds_url TEXT,
87 indexed_at TIMESTAMPTZ
88);
89```
90
91**aggregators** - Aggregator-specific metadata
92```sql
93CREATE TABLE aggregators (
94 did TEXT PRIMARY KEY,
95 display_name TEXT NOT NULL,
96 description TEXT,
97 avatar_url TEXT,
98 config_schema JSONB,
99 source_url TEXT,
100 maintainer_did TEXT,
101 record_uri TEXT NOT NULL UNIQUE,
102 record_cid TEXT NOT NULL,
103 created_at TIMESTAMPTZ,
104 indexed_at TIMESTAMPTZ
105);
106```
107
108**aggregator_authorizations** - Community authorizations
109```sql
110CREATE TABLE aggregator_authorizations (
111 id BIGSERIAL PRIMARY KEY,
112 aggregator_did TEXT NOT NULL,
113 community_did TEXT NOT NULL,
114 enabled BOOLEAN NOT NULL DEFAULT true,
115 config JSONB,
116 created_by TEXT,
117 record_uri TEXT NOT NULL UNIQUE,
118 record_cid TEXT NOT NULL,
119 UNIQUE(aggregator_did, community_did)
120);
121```
122
123## Prerequisites
124
1251. **Domain ownership**: You must own a domain where you can host static files over HTTPS
1262. **Web server**: Ability to serve the `.well-known/atproto-did` file
1273. **Development tools**: `curl`, `jq`, basic shell scripting knowledge
1284. **Email address**: For creating the PDS account
129
130**Optional**:
131- Custom avatar image (PNG/JPEG/WebP, max 1MB)
132- GitHub repository for source code transparency
133
134## Quick Start
135
136We provide automated setup scripts:
137
138```bash
139cd scripts/aggregator-setup
140
141# Make scripts executable
142chmod +x *.sh
143
144# Run setup scripts in order
145./1-create-pds-account.sh
146./2-setup-wellknown.sh
147# (Upload .well-known to your web server)
148./3-register-with-coves.sh
149./4-create-service-declaration.sh
150```
151
152See [scripts/aggregator-setup/README.md](../../scripts/aggregator-setup/README.md) for detailed script documentation.
153
154## Detailed Setup Steps
155
156### Step 1: Create PDS Account
157
158Your aggregator needs its own atProto identity (DID). The easiest way is to create an account on an existing PDS.
159
160**Using an existing PDS (recommended)**:
161
162```bash
163curl -X POST https://bsky.social/xrpc/com.atproto.server.createAccount \
164 -H "Content-Type: application/json" \
165 -d '{
166 "handle": "mynewsbot.bsky.social",
167 "email": "bot@example.com",
168 "password": "secure-password-here"
169 }'
170```
171
172**Response**:
173```json
174{
175 "accessJwt": "eyJ...",
176 "refreshJwt": "eyJ...",
177 "handle": "mynewsbot.bsky.social",
178 "did": "did:plc:abc123...",
179 "didDoc": {...}
180}
181```
182
183**Save these credentials securely!** You'll need the DID and access token for all subsequent operations.
184
185**Alternative**: Run your own PDS or use `did:web` (advanced).
186
187### Step 2: Prove Domain Ownership
188
189To register with Coves, you must prove you own a domain by serving your DID at `https://yourdomain.com/.well-known/atproto-did`.
190
191**Create the file**:
192
193```bash
194mkdir -p .well-known
195echo "did:plc:abc123..." > .well-known/atproto-did
196```
197
198**Upload to your web server** so it's accessible at:
199```
200https://rss-bot.example.com/.well-known/atproto-did
201```
202
203**Verify it works**:
204```bash
205curl https://rss-bot.example.com/.well-known/atproto-did
206# Should return: did:plc:abc123...
207```
208
209**Nginx configuration example**:
210```nginx
211location /.well-known/atproto-did {
212 alias /var/www/.well-known/atproto-did;
213 default_type text/plain;
214 add_header Access-Control-Allow-Origin *;
215}
216```
217
218### Step 3: Register with Coves
219
220Call the registration endpoint to register your aggregator DID with the Coves instance.
221
222**Endpoint**: `POST /xrpc/social.coves.aggregator.register`
223
224**Request**:
225```bash
226curl -X POST https://api.coves.social/xrpc/social.coves.aggregator.register \
227 -H "Content-Type: application/json" \
228 -d '{
229 "did": "did:plc:abc123...",
230 "domain": "rss-bot.example.com"
231 }'
232```
233
234**Response** (Success):
235```json
236{
237 "did": "did:plc:abc123...",
238 "handle": "mynewsbot.bsky.social",
239 "message": "Aggregator registered successfully. Next step: create a service declaration record at at://did:plc:abc123.../social.coves.aggregator.service/self"
240}
241```
242
243**What happens**:
2441. Coves fetches `https://rss-bot.example.com/.well-known/atproto-did`
2452. Verifies it contains your DID
2463. Resolves your DID to get handle and PDS URL
2474. Inserts you into the `users` table
248
249**You're now registered!** But you need to create a service declaration next.
250
251### Step 4: Create Service Declaration
252
253Write a `social.coves.aggregator.service` record to your repository. This contains metadata about your aggregator and gets indexed by Coves' Jetstream consumer.
254
255**Endpoint**: `POST https://your-pds.com/xrpc/com.atproto.repo.createRecord`
256
257**Request**:
258```bash
259curl -X POST https://bsky.social/xrpc/com.atproto.repo.createRecord \
260 -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
261 -H "Content-Type: application/json" \
262 -d '{
263 "repo": "did:plc:abc123...",
264 "collection": "social.coves.aggregator.service",
265 "rkey": "self",
266 "record": {
267 "$type": "social.coves.aggregator.service",
268 "did": "did:plc:abc123...",
269 "displayName": "RSS News Aggregator",
270 "description": "Aggregates tech news from various RSS feeds",
271 "sourceUrl": "https://github.com/yourname/rss-aggregator",
272 "maintainer": "did:plc:your-personal-did",
273 "createdAt": "2024-01-15T12:00:00Z"
274 }
275 }'
276```
277
278**Response**:
279```json
280{
281 "uri": "at://did:plc:abc123.../social.coves.aggregator.service/self",
282 "cid": "bafyrei..."
283}
284```
285
286**Optional fields**:
287- `avatar`: Blob reference to avatar image
288- `configSchema`: JSON Schema for community-specific configuration
289
290**Wait 5-10 seconds** for Jetstream to index your service declaration into the `aggregators` table.
291
292## Authorization Process
293
294Before you can post to a community, a moderator must authorize your aggregator.
295
296### How Authorization Works
297
2981. **Moderator decision**: Community moderator evaluates your aggregator
2992. **Authorization record**: Moderator writes `social.coves.aggregator.authorization` to community's repo
3003. **Jetstream indexing**: Record gets indexed into `aggregator_authorizations` table
3014. **Posting enabled**: You can now post to that community
302
303### Authorization Record Structure
304
305**Location**: `at://{community_did}/social.coves.aggregator.authorization/{rkey}`
306
307**Example**:
308```json
309{
310 "$type": "social.coves.aggregator.authorization",
311 "aggregatorDid": "did:plc:abc123...",
312 "communityDid": "did:plc:community123...",
313 "enabled": true,
314 "createdBy": "did:plc:moderator...",
315 "createdAt": "2024-01-15T12:00:00Z",
316 "config": {
317 "maxPostsPerHour": 5,
318 "allowedCategories": ["tech", "news"]
319 }
320}
321```
322
323### Checking Your Authorizations
324
325**Endpoint**: `GET /xrpc/social.coves.aggregator.getAuthorizations`
326
327```bash
328curl "https://api.coves.social/xrpc/social.coves.aggregator.getAuthorizations?aggregatorDid=did:plc:abc123...&enabledOnly=true"
329```
330
331**Response**:
332```json
333{
334 "authorizations": [
335 {
336 "aggregatorDid": "did:plc:abc123...",
337 "communityDid": "did:plc:community123...",
338 "communityHandle": "~tech@coves.social",
339 "enabled": true,
340 "createdAt": "2024-01-15T12:00:00Z",
341 "config": {...}
342 }
343 ]
344}
345```
346
347## Posting to Communities
348
349Once authorized, you can post to communities using the standard post creation endpoint.
350
351### Create Post
352
353**Endpoint**: `POST /xrpc/social.coves.community.post.create`
354
355**Request**:
356```bash
357curl -X POST https://api.coves.social/xrpc/social.coves.community.post.create \
358 -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
359 -H "Content-Type: application/json" \
360 -d '{
361 "communityDid": "did:plc:community123...",
362 "post": {
363 "text": "New blog post: Understanding atProto Identity\nhttps://example.com/post",
364 "createdAt": "2024-01-15T12:00:00Z",
365 "facets": [
366 {
367 "index": { "byteStart": 50, "byteEnd": 75 },
368 "features": [
369 {
370 "$type": "social.coves.richtext.facet#link",
371 "uri": "https://example.com/post"
372 }
373 ]
374 }
375 ]
376 }
377 }'
378```
379
380**Response**:
381```json
382{
383 "uri": "at://did:plc:abc123.../social.coves.community.post/3k...",
384 "cid": "bafyrei..."
385}
386```
387
388### Post Validation
389
390The handler validates:
3911. **Authentication**: Valid JWT token
3922. **Author exists**: DID in `users` table
3933. **Is aggregator**: DID in `aggregators` table
3944. **Authorization**: Active authorization for (aggregator, community)
3955. **Rate limit**: Less than 10 posts/hour to this community
3966. **Content**: Valid post structure per lexicon
397
398### Rate Limits
399
400**Per-community rate limit**: 10 posts per hour
401
402This is tracked in the `aggregator_posts` table and enforced at the handler level.
403
404**Why?**: Prevents spam while allowing useful bot activity.
405
406**Best practices**:
407- Batch similar content
408- Post only high-quality content
409- Respect community guidelines
410- Monitor your posting rate
411
412## Security Best Practices
413
414### Credential Management
415
416✅ **DO**:
417- Store credentials in environment variables or secret management
418- Use HTTPS for all API calls
419- Rotate access tokens regularly (use refresh tokens)
420- Keep `aggregator-config.env` out of version control
421
422❌ **DON'T**:
423- Hardcode credentials in source code
424- Commit credentials to Git
425- Share access tokens publicly
426- Reuse personal credentials for bots
427
428### Domain Security
429
430✅ **DO**:
431- Use HTTPS for `.well-known` endpoint
432- Keep domain under your control
433- Monitor for unauthorized changes
434- Use DNSSEC if possible
435
436❌ **DON'T**:
437- Use HTTP (will fail verification)
438- Use shared/untrusted hosting
439- Allow others to modify `.well-known` files
440- Use expired SSL certificates
441
442### Content Security
443
444✅ **DO**:
445- Validate all external content before posting
446- Sanitize URLs and text
447- Rate-limit your own posting
448- Implement circuit breakers for failures
449
450❌ **DON'T**:
451- Post unvalidated user input
452- Include malicious links
453- Spam communities
454- Bypass rate limits
455
456## Troubleshooting
457
458### Registration Errors
459
460#### Error: "DomainVerificationFailed"
461
462**Cause**: `.well-known/atproto-did` not accessible or contains wrong DID
463
464**Solutions**:
4651. Verify file is accessible: `curl https://yourdomain.com/.well-known/atproto-did`
4662. Check content matches your DID exactly (no extra whitespace)
4673. Ensure HTTPS is working (not HTTP)
4684. Check web server logs for access errors
4695. Verify firewall rules allow HTTPS traffic
470
471#### Error: "AlreadyRegistered"
472
473**Cause**: This DID is already registered with this Coves instance
474
475**Solutions**:
476- This is safe to ignore if you're re-running setup
477- If you need to update info, just create a new service declaration
478- Contact instance admin if you need to remove registration
479
480#### Error: "DIDResolutionFailed"
481
482**Cause**: Could not resolve DID document from PLC directory
483
484**Solutions**:
4851. Verify DID exists: `curl https://plc.directory/{your-did}`
4862. Wait 30 seconds and retry (PLC propagation delay)
4873. Check PDS is accessible
4884. Verify DID format is correct (must start with `did:plc:` or `did:web:`)
489
490### Posting Errors
491
492#### Error: "NotAuthorized"
493
494**Cause**: No active authorization for this (aggregator, community) pair
495
496**Solutions**:
4971. Check authorizations: `GET /xrpc/social.coves.aggregator.getAuthorizations`
4982. Contact community moderator to request authorization
4993. Verify authorization wasn't disabled
5004. Wait for Jetstream to index authorization (5-10 seconds)
501
502#### Error: "RateLimitExceeded"
503
504**Cause**: Exceeded 10 posts/hour to this community
505
506**Solutions**:
5071. Wait for the rate limit window to reset
5082. Batch posts to stay under limit
5093. Distribute posts across multiple communities
5104. Implement posting queue in your aggregator
511
512### Service Declaration Not Appearing
513
514**Symptoms**: Service declaration created but not in `aggregators` table
515
516**Solutions**:
5171. Wait 5-10 seconds for Jetstream to index
5182. Check Jetstream consumer logs for errors
5193. Verify record was created: Check PDS at `at://your-did/social.coves.aggregator.service/self`
5204. Verify `$type` field is exactly `"social.coves.aggregator.service"`
5215. Check `displayName` is not empty (required field)
522
523## API Reference
524
525### Registration Endpoint
526
527**`POST /xrpc/social.coves.aggregator.register`**
528
529**Input**:
530```typescript
531{
532 did: string // DID of aggregator (did:plc or did:web)
533 domain: string // Domain serving .well-known/atproto-did
534}
535```
536
537**Output**:
538```typescript
539{
540 did: string // Registered DID
541 handle: string // Handle from DID document
542 message: string // Next steps message
543}
544```
545
546**Errors**:
547- `InvalidDID`: DID format invalid
548- `DomainVerificationFailed`: .well-known verification failed
549- `AlreadyRegistered`: DID already registered
550- `DIDResolutionFailed`: Could not resolve DID
551
552### Query Endpoints
553
554**`GET /xrpc/social.coves.aggregator.getServices`**
555
556Get aggregator service details.
557
558**Parameters**:
559- `dids`: Array of DIDs (comma-separated)
560
561**`GET /xrpc/social.coves.aggregator.getAuthorizations`**
562
563List communities that authorized an aggregator.
564
565**Parameters**:
566- `aggregatorDid`: Aggregator DID
567- `enabledOnly`: Filter to enabled only (default: false)
568
569**`GET /xrpc/social.coves.aggregator.listForCommunity`**
570
571List aggregators authorized by a community.
572
573**Parameters**:
574- `communityDid`: Community DID
575- `enabledOnly`: Filter to enabled only (default: false)
576
577## Further Reading
578
579- [Aggregator PRD](PRD_AGGREGATORS.md) - Architecture and design decisions
580- [atProto Guide](../../ATPROTO_GUIDE.md) - atProto fundamentals
581- [Communities PRD](../PRD_COMMUNITIES.md) - Community system overview
582- [Setup Scripts README](../../scripts/aggregator-setup/README.md) - Script documentation
583
584## Support
585
586For issues or questions:
587
5881. Check this guide's troubleshooting section
5892. Review the PRD and architecture docs
5903. Check Coves GitHub issues
5914. Ask in Coves developer community