···11+# Hold Service XRPC Endpoints
22+33+This document lists all XRPC endpoints implemented in the Hold service (`pkg/hold/`).
44+55+## PDS Endpoints (`pkg/hold/pds/xrpc.go`)
66+77+### Public (No Auth Required)
88+99+| Endpoint | Method | Description |
1010+|----------|--------|-------------|
1111+| `/xrpc/_health` | GET | Health check |
1212+| `/xrpc/com.atproto.server.describeServer` | GET | Server metadata |
1313+| `/xrpc/com.atproto.repo.describeRepo` | GET | Repository information |
1414+| `/xrpc/com.atproto.repo.getRecord` | GET | Retrieve a single record |
1515+| `/xrpc/com.atproto.repo.listRecords` | GET | List records in a collection (paginated) |
1616+| `/xrpc/com.atproto.sync.listRepos` | GET | List all repositories |
1717+| `/xrpc/com.atproto.sync.getRecord` | GET | Get record as CAR file |
1818+| `/xrpc/com.atproto.sync.getRepo` | GET | Full repository as CAR file |
1919+| `/xrpc/com.atproto.sync.getRepoStatus` | GET | Repository hosting status |
2020+| `/xrpc/com.atproto.sync.subscribeRepos` | GET | WebSocket firehose |
2121+| `/xrpc/com.atproto.identity.resolveHandle` | GET | Resolve handle to DID |
2222+| `/xrpc/app.bsky.actor.getProfile` | GET | Get actor profile |
2323+| `/xrpc/app.bsky.actor.getProfiles` | GET | Get multiple profiles |
2424+| `/.well-known/did.json` | GET | DID document |
2525+| `/.well-known/atproto-did` | GET | DID for handle resolution |
2626+2727+### Conditional Auth (based on captain.public)
2828+2929+| Endpoint | Method | Description |
3030+|----------|--------|-------------|
3131+| `/xrpc/com.atproto.sync.getBlob` | GET/HEAD | Get blob (routes OCI vs ATProto) |
3232+3333+### Owner/Crew Admin Required
3434+3535+| Endpoint | Method | Description |
3636+|----------|--------|-------------|
3737+| `/xrpc/com.atproto.repo.deleteRecord` | POST | Delete a record |
3838+| `/xrpc/com.atproto.repo.uploadBlob` | POST | Upload ATProto blob |
3939+4040+### DPoP Auth Required
4141+4242+| Endpoint | Method | Description |
4343+|----------|--------|-------------|
4444+| `/xrpc/io.atcr.hold.requestCrew` | POST | Request crew membership |
4545+4646+---
4747+4848+## OCI Multipart Upload Endpoints (`pkg/hold/oci/xrpc.go`)
4949+5050+All require `blob:write` permission via service token:
5151+5252+| Endpoint | Method | Description |
5353+|----------|--------|-------------|
5454+| `/xrpc/io.atcr.hold.initiateUpload` | POST | Start multipart upload |
5555+| `/xrpc/io.atcr.hold.getPartUploadUrl` | POST | Get presigned URL for part |
5656+| `/xrpc/io.atcr.hold.uploadPart` | PUT | Direct buffered part upload |
5757+| `/xrpc/io.atcr.hold.completeUpload` | POST | Finalize multipart upload |
5858+| `/xrpc/io.atcr.hold.abortUpload` | POST | Cancel multipart upload |
5959+| `/xrpc/io.atcr.hold.notifyManifest` | POST | Notify manifest push (creates layer records + optional Bluesky post) |
6060+6161+---
6262+6363+## Standard ATProto Endpoints (excluding io.atcr.hold.*)
6464+6565+| Endpoint |
6666+|----------|
6767+| /xrpc/_health |
6868+| /xrpc/com.atproto.server.describeServer |
6969+| /xrpc/com.atproto.repo.describeRepo |
7070+| /xrpc/com.atproto.repo.getRecord |
7171+| /xrpc/com.atproto.repo.listRecords |
7272+| /xrpc/com.atproto.repo.deleteRecord |
7373+| /xrpc/com.atproto.repo.uploadBlob |
7474+| /xrpc/com.atproto.sync.listRepos |
7575+| /xrpc/com.atproto.sync.getRecord |
7676+| /xrpc/com.atproto.sync.getRepo |
7777+| /xrpc/com.atproto.sync.getRepoStatus |
7878+| /xrpc/com.atproto.sync.getBlob |
7979+| /xrpc/com.atproto.sync.subscribeRepos |
8080+| /xrpc/com.atproto.identity.resolveHandle |
8181+| /xrpc/app.bsky.actor.getProfile |
8282+| /xrpc/app.bsky.actor.getProfiles |
8383+| /.well-known/did.json |
8484+| /.well-known/atproto-did |
+3-3
pkg/appview/middleware/registry.go
···460460 Repository: repositoryName,
461461 ServiceToken: serviceToken, // Cached service token from puller's PDS
462462 ATProtoClient: atprotoClient,
463463- AuthMethod: authMethod, // Auth method from JWT token
464464- PullerDID: pullerDID, // Authenticated user making the request
465465- PullerPDSEndpoint: pullerPDSEndpoint, // Puller's PDS for service token refresh
463463+ AuthMethod: authMethod, // Auth method from JWT token
464464+ PullerDID: pullerDID, // Authenticated user making the request
465465+ PullerPDSEndpoint: pullerPDSEndpoint, // Puller's PDS for service token refresh
466466 Database: nr.database,
467467 Authorizer: nr.authorizer,
468468 Refresher: nr.refresher,
+10-10
pkg/appview/storage/context.go
···2020 // Per-request identity and routing information
2121 // Owner = the user whose repository is being accessed
2222 // Puller = the authenticated user making the request (from JWT Subject)
2323- DID string // Owner's DID - whose repo is being accessed (e.g., "did:plc:abc123")
2424- Handle string // Owner's handle (e.g., "alice.bsky.social")
2525- HoldDID string // Hold service DID (e.g., "did:web:hold01.atcr.io")
2626- PDSEndpoint string // Owner's PDS endpoint URL
2727- Repository string // Image repository name (e.g., "debian")
2828- ServiceToken string // Service token for hold authentication (from puller's PDS)
2929- ATProtoClient *atproto.Client // Authenticated ATProto client for the owner
3030- AuthMethod string // Auth method used ("oauth" or "app_password")
3131- PullerDID string // Puller's DID - who is making the request (from JWT Subject)
3232- PullerPDSEndpoint string // Puller's PDS endpoint URL
2323+ DID string // Owner's DID - whose repo is being accessed (e.g., "did:plc:abc123")
2424+ Handle string // Owner's handle (e.g., "alice.bsky.social")
2525+ HoldDID string // Hold service DID (e.g., "did:web:hold01.atcr.io")
2626+ PDSEndpoint string // Owner's PDS endpoint URL
2727+ Repository string // Image repository name (e.g., "debian")
2828+ ServiceToken string // Service token for hold authentication (from puller's PDS)
2929+ ATProtoClient *atproto.Client // Authenticated ATProto client for the owner
3030+ AuthMethod string // Auth method used ("oauth" or "app_password")
3131+ PullerDID string // Puller's DID - who is making the request (from JWT Subject)
3232+ PullerPDSEndpoint string // Puller's PDS endpoint URL
33333434 // Shared services (same for all requests)
3535 Database DatabaseMetrics // Metrics tracking database
-1
pkg/atproto/lexicon.go
···310310 CreatedAt time.Time `json:"createdAt"`
311311}
312312313313-314313// SailorProfileRecord represents a user's profile with registry preferences
315314// Stored in the user's PDS to configure default hold and other settings
316315type SailorProfileRecord struct {