Skip to main content

PDS Forwarding

As a convenience for local-first applications, habitat proxies a scoped set of standard AT Protocol XRPC calls on behalf of authenticated users. Rather than forwarding all unknown /xrpc/ paths, Habitat explicitly handles two namespaces and one additional method.

Which requests are forwarded

com.atproto.repo.* and com.atproto.sync.*

All methods under these two namespaces are forwarded. Habitat routes them in one of two ways depending on whether the method reads from a specific target repository or writes to the caller's own:

Forwarded to the caller's PDS (authenticated, via DPoP):

  • Write methods: createRecord, putRecord, applyWrites, uploadBlob, importRepo
  • com.atproto.sync methods with no specific target: listRepos, listReposByCollection, requestCrawl

Forwarded to the target's PDS (unauthenticated):

These methods accept a repo or did query parameter identifying a specific account. Habitat resolves that identity's DID document to find their PDS and forwards the request there without auth headers.

MethodTarget param
com.atproto.repo.getRecordrepo
com.atproto.repo.listRecordsrepo
com.atproto.repo.describeReporepo
com.atproto.sync.getRepodid
com.atproto.sync.getRecorddid
com.atproto.sync.listBlobsdid
com.atproto.sync.getLatestCommitdid
com.atproto.sync.getRepoStatusdid
com.atproto.sync.getBlobdid

com.atproto.server.getServiceAuth

Forwarded to the caller's PDS (authenticated, via DPoP). Used to obtain a signed service auth token from the user's own PDS for inter-service calls.

Methods NOT forwarded through Habitat

Everything else. For example, app.bsky.* and com.atproto.identity.* methods should be called directly against the public Bluesky AppView:

https://public.api.bsky.app/xrpc/<lexicon-id>

or the user's PDS directly. These are public, unauthenticated endpoints. No authentication token is needed.

How forwarding works

For calls routed to the caller's PDS:

  1. Habitat validates your access token and resolves the caller's DID
  2. Habitat creates a DPoP-authenticated HTTP client for that user's PDS
  3. The request (method, headers, body) is forwarded to the user's PDS
  4. The PDS response (status, headers, body) is streamed back to your app

Your app never directly authenticates with the PDS. Habitat holds encrypted PDS credentials server-side (obtained during habitat sign-up/login) and handles the DPoP token binding required by AT Protocol PDSes.

For calls routed to the target's PDS:

  1. Habitat extracts the repo or did query parameter
  2. Habitat resolves the target's DID document to find their atproto_pds service endpoint
  3. The request is forwarded to that PDS without auth headers (these are public endpoints)

Making a forwarded request

GET /xrpc/com.atproto.repo.listRecords?repo=did:plc:...&collection=app.bsky.feed.post
Authorization: Bearer <habitat-access-token>
Habitat-Auth-Method: oauth

For target-routed read requests, auth headers are not required (and are stripped before forwarding):

GET /xrpc/com.atproto.repo.getRecord?repo=did:plc:...&collection=app.bsky.feed.post&rkey=...

Error handling

ConditionStatus returned
Invalid or expired Habitat token (caller-PDS path)401 Unauthorized
Missing repo/did param on target-routed method400 Bad Request
Target DID cannot be resolved502 Bad Gateway
Target has no atproto_pds service in their DID doc502 Bad Gateway
PDS is unreachable or returns an unexpected error502 Bad Gateway
Other forwarding errors500 Internal Server Error

The response body from the PDS (including AT Protocol error payloads) is passed through unchanged.