A community based topic aggregation platform built on atproto
1# Federation PRD: Cross-Instance Posting (Beta)
2
3**Status:** Planning - Beta
4**Target:** Beta Release
5**Owner:** TBD
6**Last Updated:** 2025-11-16
7
8---
9
10## Overview
11
12Enable Lemmy-style federation where users on any Coves instance can post to communities hosted on other instances, while maintaining community ownership and moderation control.
13
14### Problem Statement
15
16**Current (Alpha):**
17- Posts to communities require community credentials
18- Users can only post to communities on their home instance
19- No true federation across instances
20
21**Desired (Beta):**
22- User A@coves.social can post to !gaming@covesinstance.com
23- Communities maintain full moderation control
24- Content lives in community repositories (not user repos)
25- Seamless UX - users don't think about federation
26
27---
28
29## Goals
30
31### Primary Goals
321. **Enable cross-instance posting** - Users can post to any community on any federated instance
332. **Preserve community ownership** - Posts live in community repos, not user repos
343. **atProto-native implementation** - Use `com.atproto.server.getServiceAuth` pattern
354. **Maintain security** - No compromise on auth, validation, or moderation
36
37### Non-Goals (Future Versions)
38- Automatic instance discovery (Beta: manual allowlist)
39- Cross-instance moderation delegation
40- Content mirroring/replication
41- User migration between instances
42
43---
44
45## Technical Approach
46
47### Architecture: atProto Service Auth
48
49Use atProto's native service authentication delegation pattern:
50
51```
52┌─────────────┐ ┌──────────────────┐ ┌─────────────┐
53│ User A │ │ coves.social │ │ covesinstance│
54│ @coves.soc │────────▶│ AppView │────────▶│ .com PDS │
55└─────────────┘ (1) └──────────────────┘ (2) └─────────────┘
56 JWT auth Request Service Auth Validate
57 │ │
58 │◀──────────────────────┘
59 │ (3) Scoped Token
60 │
61 ▼
62 ┌──────────────────┐
63 │ covesinstance │
64 │ .com PDS │
65 │ Write Post │
66 └──────────────────┘
67 │
68 ▼
69 ┌──────────────────┐
70 │ Firehose │
71 │ (broadcasts) │
72 └──────────────────┘
73 │
74 ┌────────────┴────────────┐
75 ▼ ▼
76 ┌──────────────┐ ┌──────────────┐
77 │ coves.social │ │covesinstance │
78 │ AppView │ │ .com AppView│
79 │ (indexes) │ │ (indexes) │
80 └──────────────┘ └──────────────┘
81```
82
83### Flow Breakdown
84
85**Step 1: User Authentication (Unchanged)**
86- User authenticates with their home instance (coves.social)
87- Receives JWT token for API requests
88
89**Step 2: Service Auth Request (New)**
90- When posting to remote community, AppView requests service auth token
91- Endpoint: `POST {remote-pds}/xrpc/com.atproto.server.getServiceAuth`
92- Payload:
93 ```json
94 {
95 "aud": "did:plc:community123", // Community DID
96 "exp": 1234567890, // Token expiration
97 "lxm": "social.coves.community.post.create" // Authorized method
98 }
99 ```
100
101**Step 3: Service Auth Validation (New - PDS Side)**
102- Remote PDS validates request:
103 - Is requesting service trusted? (instance allowlist)
104 - Is user banned from community?
105 - Does community allow remote posts?
106 - Rate limiting checks
107- Returns scoped token valid for specific community + operation
108
109**Step 4: Post Creation (Modified)**
110- AppView uses service auth token to write to remote PDS
111- Same `com.atproto.repo.createRecord` endpoint as current implementation
112- Post record written to community's repository
113
114**Step 5: Indexing (Unchanged)**
115- PDS broadcasts to firehose
116- All AppViews index via Jetstream consumers
117
118---
119
120## Implementation Details
121
122### Phase 1: Service Detection (Local vs Remote)
123
124**File:** `internal/core/posts/service.go`
125
126```go
127func (s *postService) CreatePost(ctx context.Context, req CreatePostRequest) (*CreatePostResponse, error) {
128 // ... existing validation ...
129
130 community, err := s.communityService.GetByDID(ctx, communityDID)
131 if err != nil {
132 return nil, err
133 }
134
135 // NEW: Route based on community location
136 if s.isLocalCommunity(community) {
137 return s.createLocalPost(ctx, community, req)
138 }
139 return s.createFederatedPost(ctx, community, req)
140}
141
142func (s *postService) isLocalCommunity(community *communities.Community) bool {
143 localPDSHost := extractHost(s.pdsURL)
144 communityPDSHost := extractHost(community.PDSURL)
145 return localPDSHost == communityPDSHost
146}
147```
148
149### Phase 2: Service Auth Client
150
151**New File:** `internal/atproto/service_auth/client.go`
152
153```go
154type ServiceAuthClient interface {
155 // RequestServiceAuth obtains a scoped token for writing to remote community
156 RequestServiceAuth(ctx context.Context, opts ServiceAuthOptions) (*ServiceAuthToken, error)
157}
158
159type ServiceAuthOptions struct {
160 RemotePDSURL string // Remote PDS endpoint
161 CommunityDID string // Target community DID
162 UserDID string // Author DID (for validation)
163 Method string // "social.coves.community.post.create"
164 ExpiresIn int // Token lifetime (seconds)
165}
166
167type ServiceAuthToken struct {
168 Token string // JWT token for auth
169 ExpiresAt time.Time // When token expires
170}
171
172func (c *serviceAuthClient) RequestServiceAuth(ctx context.Context, opts ServiceAuthOptions) (*ServiceAuthToken, error) {
173 endpoint := fmt.Sprintf("%s/xrpc/com.atproto.server.getServiceAuth", opts.RemotePDSURL)
174
175 payload := map[string]interface{}{
176 "aud": opts.CommunityDID,
177 "exp": time.Now().Add(time.Duration(opts.ExpiresIn) * time.Second).Unix(),
178 "lxm": opts.Method,
179 }
180
181 // Sign request with our instance DID credentials
182 signedReq, err := c.signRequest(payload)
183 if err != nil {
184 return nil, fmt.Errorf("failed to sign service auth request: %w", err)
185 }
186
187 resp, err := c.httpClient.Post(endpoint, signedReq)
188 if err != nil {
189 return nil, fmt.Errorf("service auth request failed: %w", err)
190 }
191
192 return parseServiceAuthResponse(resp)
193}
194```
195
196### Phase 3: Federated Post Creation
197
198**File:** `internal/core/posts/service.go`
199
200```go
201func (s *postService) createFederatedPost(ctx context.Context, community *communities.Community, req CreatePostRequest) (*CreatePostResponse, error) {
202 // 1. Request service auth token from remote PDS
203 token, err := s.serviceAuthClient.RequestServiceAuth(ctx, service_auth.ServiceAuthOptions{
204 RemotePDSURL: community.PDSURL,
205 CommunityDID: community.DID,
206 UserDID: req.AuthorDID,
207 Method: "social.coves.community.post.create",
208 ExpiresIn: 300, // 5 minutes
209 })
210 if err != nil {
211 // Handle specific errors
212 if isUnauthorized(err) {
213 return nil, ErrNotAuthorizedRemote
214 }
215 if isBanned(err) {
216 return nil, ErrBannedRemote
217 }
218 return nil, fmt.Errorf("failed to obtain service auth: %w", err)
219 }
220
221 // 2. Build post record (same as local)
222 postRecord := PostRecord{
223 Type: "social.coves.community.post",
224 Community: community.DID,
225 Author: req.AuthorDID,
226 Title: req.Title,
227 Content: req.Content,
228 // ... other fields ...
229 CreatedAt: time.Now().UTC().Format(time.RFC3339),
230 }
231
232 // 3. Write to remote PDS using service auth token
233 uri, cid, err := s.createPostOnRemotePDS(ctx, community.PDSURL, community.DID, postRecord, token.Token)
234 if err != nil {
235 return nil, fmt.Errorf("failed to write to remote PDS: %w", err)
236 }
237
238 log.Printf("[FEDERATION] User %s posted to remote community %s: %s",
239 req.AuthorDID, community.DID, uri)
240
241 return &CreatePostResponse{
242 URI: uri,
243 CID: cid,
244 }, nil
245}
246
247func (s *postService) createPostOnRemotePDS(
248 ctx context.Context,
249 pdsURL string,
250 communityDID string,
251 record PostRecord,
252 serviceAuthToken string,
253) (uri, cid string, err error) {
254 endpoint := fmt.Sprintf("%s/xrpc/com.atproto.repo.createRecord", pdsURL)
255
256 payload := map[string]interface{}{
257 "repo": communityDID,
258 "collection": "social.coves.community.post",
259 "record": record,
260 }
261
262 jsonData, _ := json.Marshal(payload)
263 req, _ := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewBuffer(jsonData))
264
265 // Use service auth token instead of community credentials
266 // NOTE: Auth scheme depends on target PDS implementation:
267 // - Standard atproto service auth uses "Bearer" scheme
268 // - Our AppView uses "DPoP" scheme when DPoP-bound tokens are required
269 // For server-to-server with standard PDS, use Bearer; adjust based on target.
270 req.Header.Set("Authorization", "Bearer "+serviceAuthToken)
271 req.Header.Set("Content-Type", "application/json")
272
273 // ... execute request, parse response ...
274 return uri, cid, nil
275}
276```
277
278### Phase 4: PDS Service Auth Validation (PDS Extension)
279
280**Note:** This requires extending the PDS. Options:
2811. Contribute to official atproto PDS
2822. Run modified PDS fork
2833. Use PDS middleware/proxy
284
285**Conceptual Implementation:**
286
287```go
288// PDS validates service auth requests before issuing tokens
289func (h *ServiceAuthHandler) HandleGetServiceAuth(w http.ResponseWriter, r *http.Request) {
290 var req ServiceAuthRequest
291 json.NewDecoder(r.Body).Decode(&req)
292
293 // 1. Verify requesting service is trusted
294 requestingDID := extractDIDFromJWT(r.Header.Get("Authorization"))
295 if !h.isTrustedInstance(requestingDID) {
296 writeError(w, http.StatusForbidden, "UntrustedInstance", "Instance not in allowlist")
297 return
298 }
299
300 // 2. Validate community exists on this PDS
301 community, err := h.getCommunityByDID(req.Aud)
302 if err != nil {
303 writeError(w, http.StatusNotFound, "CommunityNotFound", "Community not hosted here")
304 return
305 }
306
307 // 3. Check user not banned (query from AppView or local moderation records)
308 if h.isUserBanned(req.UserDID, req.Aud) {
309 writeError(w, http.StatusForbidden, "Banned", "User banned from community")
310 return
311 }
312
313 // 4. Check community settings (allows remote posts?)
314 if !community.AllowFederatedPosts {
315 writeError(w, http.StatusForbidden, "FederationDisabled", "Community doesn't accept federated posts")
316 return
317 }
318
319 // 5. Rate limiting (per user, per community, per instance)
320 if h.exceedsRateLimit(req.UserDID, req.Aud, requestingDID) {
321 writeError(w, http.StatusTooManyRequests, "RateLimited", "Too many requests")
322 return
323 }
324
325 // 6. Generate scoped token
326 token := h.issueServiceAuthToken(ServiceAuthTokenOptions{
327 Audience: req.Aud, // Community DID
328 Subject: requestingDID, // Requesting instance DID
329 Method: req.Lxm, // Authorized method
330 ExpiresAt: time.Unix(req.Exp, 0),
331 Scopes: []string{"write:posts"},
332 })
333
334 json.NewEncoder(w).Encode(map[string]string{
335 "token": token,
336 })
337}
338```
339
340---
341
342## Database Schema Changes
343
344### New Table: `instance_federation`
345
346Tracks trusted instances and federation settings:
347
348```sql
349CREATE TABLE instance_federation (
350 id SERIAL PRIMARY KEY,
351 instance_did TEXT NOT NULL UNIQUE,
352 instance_domain TEXT NOT NULL,
353 trust_level TEXT NOT NULL, -- 'trusted', 'limited', 'blocked'
354 allowed_methods TEXT[] NOT NULL DEFAULT '{}',
355 rate_limit_posts_per_hour INTEGER NOT NULL DEFAULT 100,
356 created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
357 updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
358 notes TEXT
359);
360
361CREATE INDEX idx_instance_federation_did ON instance_federation(instance_did);
362CREATE INDEX idx_instance_federation_trust ON instance_federation(trust_level);
363```
364
365### New Table: `federation_rate_limits`
366
367Track federated post rate limits:
368
369```sql
370CREATE TABLE federation_rate_limits (
371 id SERIAL PRIMARY KEY,
372 user_did TEXT NOT NULL,
373 community_did TEXT NOT NULL,
374 instance_did TEXT NOT NULL,
375 window_start TIMESTAMPTZ NOT NULL,
376 post_count INTEGER NOT NULL DEFAULT 1,
377 created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
378 updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
379
380 UNIQUE(user_did, community_did, instance_did, window_start)
381);
382
383CREATE INDEX idx_federation_rate_limits_lookup
384 ON federation_rate_limits(user_did, community_did, instance_did, window_start);
385```
386
387### Update Table: `communities`
388
389Add federation settings:
390
391```sql
392ALTER TABLE communities
393ADD COLUMN allow_federated_posts BOOLEAN NOT NULL DEFAULT true,
394ADD COLUMN federation_mode TEXT NOT NULL DEFAULT 'open';
395-- federation_mode: 'open' (any instance), 'allowlist' (trusted only), 'local' (no federation)
396```
397
398---
399
400## Security Considerations
401
402### 1. Instance Trust Model
403
404**Allowlist Approach (Beta):**
405- Manual approval of federated instances
406- Admin UI to manage instance trust levels
407- Default: block all, explicit allow
408
409**Trust Levels:**
410- `trusted` - Full federation, normal rate limits
411- `limited` - Federation allowed, strict rate limits
412- `blocked` - No federation
413
414### 2. User Ban Synchronization
415
416**Challenge:** Remote instance needs to check local bans
417
418**Options:**
4191. **Service auth validation** - PDS queries AppView for ban status
4202. **Ban records in PDS** - Moderation records stored in community repo
4213. **Cached ban list** - Remote instances cache ban lists (with TTL)
422
423**Beta Approach:** Option 1 (service auth validation queries AppView)
424
425### 3. Rate Limiting
426
427**Multi-level rate limits:**
428- Per user per community: 10 posts/hour
429- Per instance per community: 100 posts/hour
430- Per user across all communities: 50 posts/hour
431
432**Implementation:** In-memory + PostgreSQL for persistence
433
434### 4. Content Validation
435
436**Same validation as local posts:**
437- Lexicon validation
438- Content length limits
439- Embed validation
440- Label validation
441
442**Additional federation checks:**
443- Verify author DID is valid
444- Verify requesting instance signature
445- Verify token scopes match operation
446
447---
448
449## API Changes
450
451### New Endpoint: `social.coves.federation.getTrustedInstances`
452
453**Purpose:** List instances this instance federates with
454
455**Lexicon:**
456```json
457{
458 "lexicon": 1,
459 "id": "social.coves.federation.getTrustedInstances",
460 "defs": {
461 "main": {
462 "type": "query",
463 "output": {
464 "encoding": "application/json",
465 "schema": {
466 "type": "object",
467 "required": ["instances"],
468 "properties": {
469 "instances": {
470 "type": "array",
471 "items": { "$ref": "#instanceView" }
472 }
473 }
474 }
475 }
476 },
477 "instanceView": {
478 "type": "object",
479 "required": ["did", "domain", "trustLevel"],
480 "properties": {
481 "did": { "type": "string" },
482 "domain": { "type": "string" },
483 "trustLevel": { "type": "string" },
484 "allowedMethods": { "type": "array", "items": { "type": "string" } }
485 }
486 }
487 }
488}
489```
490
491### Modified Endpoint: `social.coves.community.post.create`
492
493**Changes:**
494- No API contract changes
495- Internal routing: local vs federated
496- New error codes:
497 - `FederationFailed` - Remote instance unreachable
498 - `RemoteNotAuthorized` - Remote instance rejected auth
499 - `RemoteBanned` - User banned on remote community
500
501---
502
503## User Experience
504
505### Happy Path: Cross-Instance Post
506
5071. User on coves.social navigates to !gaming@covesinstance.com
5082. Clicks "Create Post"
5093. Fills out post form (title, content, etc.)
5104. Clicks "Submit"
5115. **Behind the scenes:**
512 - coves.social requests service auth from covesinstance.com
513 - covesinstance.com validates and issues token
514 - coves.social writes post using token
515 - Post appears in feed within seconds (via firehose)
5166. **User sees:** Post published successfully
5177. Post appears in:
518 - covesinstance.com feeds (native community)
519 - coves.social discover/all feeds (indexed via firehose)
520 - User's profile on coves.social
521
522### Error Cases
523
524**User Banned:**
525- Error: "You are banned from !gaming@covesinstance.com"
526- Suggestion: "Contact community moderators for more information"
527
528**Instance Blocked:**
529- Error: "This community does not accept posts from your instance"
530- Suggestion: "Contact community administrators or create a local account"
531
532**Federation Unavailable:**
533- Error: "Unable to connect to covesinstance.com. Try again later."
534- Fallback: Allow saving as draft (future feature)
535
536**Rate Limited:**
537- Error: "You're posting too quickly. Please wait before posting again."
538- Show: Countdown until next post allowed
539
540---
541
542## Testing Requirements
543
544### Unit Tests
545
5461. **Service Detection:**
547 - `isLocalCommunity()` correctly identifies local vs remote
548 - Handles edge cases (different ports, subdomains)
549
5502. **Service Auth Client:**
551 - Correctly formats service auth requests
552 - Handles token expiration
553 - Retries on transient failures
554
5553. **Federated Post Creation:**
556 - Uses service auth token instead of community credentials
557 - Falls back gracefully on errors
558 - Logs federation events
559
560### Integration Tests
561
5621. **Local Post (Regression):**
563 - Posting to local community still works
564 - No performance degradation
565
5662. **Federated Post:**
567 - User can post to remote community
568 - Service auth token requested correctly
569 - Post written to remote PDS
570 - Post indexed by both AppViews
571
5723. **Authorization Failures:**
573 - Banned users rejected at service auth stage
574 - Untrusted instances rejected
575 - Expired tokens rejected
576
5774. **Rate Limiting:**
578 - Per-user rate limits enforced
579 - Per-instance rate limits enforced
580 - Rate limit resets correctly
581
582### End-to-End Tests
583
5841. **Cross-Instance User Journey:**
585 - Set up two instances (instance-a, instance-b)
586 - Create community on instance-b
587 - User on instance-a posts to instance-b community
588 - Verify post appears on both instances
589
5902. **Moderation Enforcement:**
591 - Ban user on remote instance
592 - Verify user can't post from any instance
593 - Unban user
594 - Verify user can post again
595
5963. **Instance Blocklist:**
597 - Block instance-a on instance-b
598 - Verify users from instance-a can't post to instance-b communities
599 - Unblock instance-a
600 - Verify posting works again
601
602---
603
604## Migration Path (Alpha → Beta)
605
606### Phase 1: Backend Implementation (No User Impact)
6071. Add service auth client
6082. Add local vs remote detection
6093. Deploy with feature flag `ENABLE_FEDERATION=false`
610
611### Phase 2: Database Migration
6121. Add federation tables
6132. Seed with initial trusted instances (manual)
6143. Add community federation flags (default: allow)
615
616### Phase 3: Soft Launch
6171. Enable federation for single test instance
6182. Monitor service auth requests/errors
6193. Validate rate limiting works
620
621### Phase 4: Beta Rollout
6221. Enable `ENABLE_FEDERATION=true` for all instances
6232. Admin UI for managing trusted instances
6243. Community settings for federation preferences
625
626### Phase 5: Documentation & Onboarding
6271. Instance operator guide: "How to federate with other instances"
6282. Community moderator guide: "Federation settings"
6293. User guide: "Posting across instances"
630
631---
632
633## Metrics & Success Criteria
634
635### Performance Metrics
636- Service auth request latency: p95 < 200ms
637- Federated post creation time: p95 < 2 seconds (vs 500ms local)
638- Service auth token cache hit rate: > 80%
639
640### Adoption Metrics
641- % of posts that are federated: Target 20% by end of Beta
642- Number of federated instances: Target 5+ by end of Beta
643- Cross-instance engagement (comments, votes): Monitor trend
644
645### Reliability Metrics
646- Service auth success rate: > 99%
647- Federated post success rate: > 95%
648- Service auth token validation errors: < 1%
649
650### Security Metrics
651- Unauthorized access attempts: Monitor & alert
652- Rate limit triggers: Track per instance
653- Ban evasion attempts: Zero tolerance
654
655---
656
657## Rollback Plan
658
659If federation causes critical issues:
660
6611. **Immediate:** Set `ENABLE_FEDERATION=false` via env var
6622. **Fallback:** All posts route through local-only flow
6633. **Investigation:** Review logs for service auth failures
6644. **Fix Forward:** Deploy patch, re-enable gradually
665
666**No data loss:** Posts are written to PDS, indexed via firehose regardless of federation method.
667
668---
669
670## Open Questions
671
6721. **Instance Discovery:** How do users find communities on other instances?
673 - Beta: Manual (users share links)
674 - Future: Instance directory, community search across instances
675
6762. **Service Auth Token Caching:** Should AppViews cache service auth tokens?
677 - Pros: Reduce latency, fewer PDS requests
678 - Cons: Stale permissions, ban enforcement delay
679 - **Decision needed:** Cache with short TTL (5 minutes)?
680
6813. **PDS Implementation:** Who implements service auth validation?
682 - Option A: Contribute to official PDS (long timeline)
683 - Option B: Run forked PDS (maintenance burden)
684 - Option C: Proxy/middleware (added complexity)
685 - **Decision needed:** Start with Option B, migrate to Option A?
686
6874. **Federation Symmetry:** If instance-a trusts instance-b, does instance-b auto-trust instance-a?
688 - Beta: No (asymmetric trust)
689 - Future: Mutual federation agreements?
690
6915. **Cross-Instance Moderation:** Should bans propagate across instances?
692 - Beta: No (each instance decides)
693 - Future: Shared moderation lists?
694
695---
696
697## Future Enhancements (Post-Beta)
698
6991. **Service Auth Token Caching:** Reduce latency for frequent posters
7002. **Batch Service Auth:** Request tokens for multiple communities at once
7013. **Instance Discovery API:** Automatic instance detection/registration
7024. **Federation Analytics:** Dashboard showing cross-instance activity
7035. **Moderation Sync:** Optional shared ban lists across trusted instances
7046. **Content Mirroring:** Cache federated posts locally for performance
7057. **User Migration:** Transfer account between instances
706
707---
708
709## Resources
710
711### Documentation
712- [atProto Service Auth Spec](https://atproto.com/specs/service-auth) (hypothetical - check actual docs)
713- Lemmy Federation Architecture
714- Mastodon Federation Implementation
715
716### Code References
717- `internal/core/posts/service.go` - Post creation service
718- `internal/api/handlers/post/create.go` - Post creation handler
719- `internal/atproto/jetstream/` - Firehose consumers
720
721### Dependencies
722- atproto SDK (for service auth)
723- PDS v0.4+ (service auth support)
724- PostgreSQL 14+ (for federation tables)
725
726---
727
728## Appendix A: Service Auth Request Example
729
730**Request to Remote PDS:**
731```http
732POST https://covesinstance.com/xrpc/com.atproto.server.getServiceAuth
733Authorization: DPoP {coves-social-instance-jwt}
734DPoP: {coves-social-dpop-proof}
735Content-Type: application/json
736
737{
738 "aud": "did:plc:community123",
739 "exp": 1700000000,
740 "lxm": "social.coves.community.post.create"
741}
742```
743
744**Response:**
745```http
746HTTP/1.1 200 OK
747Content-Type: application/json
748
749{
750 "token": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9..."
751}
752```
753
754**Using Token to Create Post:**
755```http
756POST https://covesinstance.com/xrpc/com.atproto.repo.createRecord
757Authorization: DPoP {service-auth-token}
758DPoP: {service-auth-dpop-proof}
759Content-Type: application/json
760
761{
762 "repo": "did:plc:community123",
763 "collection": "social.coves.community.post",
764 "record": {
765 "$type": "social.coves.community.post",
766 "community": "did:plc:community123",
767 "author": "did:plc:user456",
768 "title": "Hello from coves.social!",
769 "content": "This is a federated post",
770 "createdAt": "2024-11-16T12:00:00Z"
771 }
772}
773```
774
775---
776
777## Appendix B: Error Handling Matrix
778
779| Error Condition | HTTP Status | Error Code | User Message | Retry Strategy |
780|----------------|-------------|------------|--------------|----------------|
781| Instance not trusted | 403 | `UntrustedInstance` | "This community doesn't accept posts from your instance" | No retry |
782| User banned | 403 | `Banned` | "You are banned from this community" | No retry |
783| Rate limit exceeded | 429 | `RateLimited` | "Too many posts. Try again in X minutes" | Exponential backoff |
784| PDS unreachable | 503 | `ServiceUnavailable` | "Community temporarily unavailable" | Retry 3x with backoff |
785| Invalid token | 401 | `InvalidToken` | "Session expired. Please try again" | Refresh token & retry |
786| Community not found | 404 | `CommunityNotFound` | "Community not found" | No retry |
787| Service auth failed | 500 | `FederationFailed` | "Unable to connect. Try again later" | Retry 2x |
788
789---
790
791**End of PRD**