- diff view gains a Packages tab with added/removed/changed/unchanged package tables and purl-derived type/license/upstream links - captain records verified against the DID's atcr_hold service before caching (processor + batch backfill), preventing forged holds - fix empty-handle updates clobbering cached handles and colliding on the UNIQUE constraint - move fillPrevCIDs into repo.go; DirectRepoOperator is now canonical, repomgr kept as a test oracle - surface read-only crew status in hold selector - reconcile docs
7.4 KiB
Hold Service XRPC Endpoints
This document lists all XRPC endpoints implemented in the Hold service (pkg/hold/).
PDS Endpoints (pkg/hold/pds/xrpc.go)
Public (No Auth Required)
| Endpoint | Method | Description |
|---|---|---|
/xrpc/_health |
GET | Health check |
/xrpc/com.atproto.server.describeServer |
GET | Server metadata |
/xrpc/com.atproto.repo.describeRepo |
GET | Repository information |
/xrpc/com.atproto.repo.getRecord |
GET | Retrieve a single record |
/xrpc/com.atproto.repo.listRecords |
GET | List records in a collection (paginated) |
/xrpc/com.atproto.sync.listBlobs |
GET | List blob CIDs for an account |
/xrpc/com.atproto.sync.listRepos |
GET | List all repositories |
/xrpc/com.atproto.sync.getRecord |
GET | Get record as CAR file |
/xrpc/com.atproto.sync.getRepo |
GET | Full repository as CAR file |
/xrpc/com.atproto.sync.getRepoStatus |
GET | Repository hosting status |
/xrpc/com.atproto.sync.getLatestCommit |
GET | Current commit CID and revision |
/xrpc/com.atproto.sync.subscribeRepos |
GET | WebSocket firehose |
/xrpc/com.atproto.identity.resolveHandle |
GET | Resolve handle to DID |
/xrpc/app.bsky.actor.getProfile |
GET | Get actor profile |
/xrpc/app.bsky.actor.getProfiles |
GET | Get multiple profiles |
/xrpc/io.atcr.hold.listTiers |
GET | List hold's available tiers with quotas and features |
/.well-known/did.json |
GET | DID document |
/.well-known/atproto-did |
GET | DID for handle resolution |
Conditional Auth (based on captain.public)
| Endpoint | Method | Description |
|---|---|---|
/xrpc/com.atproto.sync.getBlob |
GET/HEAD | Get blob (routes OCI vs ATProto) |
Owner/Crew Admin Required
| Endpoint | Method | Description |
|---|---|---|
/xrpc/com.atproto.repo.deleteRecord |
POST | Delete a record |
/xrpc/com.atproto.repo.uploadBlob |
POST | Upload ATProto blob |
Inline Auth (per-manifest caller check)
/xrpc/io.atcr.hold.purgeManifest (POST) does not use a router middleware. Auth is validated inline by ValidateManifestPurger, which accepts either a Bearer service token or a DPoP token, then checks the caller's role:
- Hold captain (any manifest)
- Crew member with
crew:adminpermission (any manifest) - Crew member whose DID matches the manifest URI's DID (own manifests only)
Idempotent. Does not delete S3 blobs — GC handles those.
Auth Required (Service Token)
The requireAuth middleware validates Bearer service tokens only. requestCrew additionally accepts DPoP tokens when called directly (the handler falls back to ValidateDPoPRequest if no user is in context).
| Endpoint | Method | Description |
|---|---|---|
/xrpc/io.atcr.hold.requestCrew |
POST | Request crew membership (service token or DPoP) |
/xrpc/io.atcr.hold.exportUserData |
GET | GDPR data export (returns user's records; service token only) |
/xrpc/io.atcr.hold.deleteUserData |
DELETE | GDPR data deletion (deletes crew, layer, and stats records; service token only) |
Appview Token Required
/xrpc/io.atcr.hold.updateCrewTier (POST) validates the caller inline via ValidateAppviewToken. Returns 503 if the appview DID is not configured on the hold, or 401 on token validation failure.
| Endpoint | Method | Description |
|---|---|---|
/xrpc/io.atcr.hold.updateCrewTier |
POST | Update a crew member's tier (appview JWT, ES256) |
Scanner WebSocket
| Endpoint | Method | Description |
|---|---|---|
/xrpc/io.atcr.hold.subscribeScanJobs |
GET (WebSocket) | Scanner job subscription. Auth via ?secret= query param or X-Scanner-Secret header (shared secret). Supports ?cursor= for backfill. |
OCI Multipart Upload Endpoints (pkg/hold/oci/xrpc.go)
All require blob:write permission via service token:
| Endpoint | Method | Description |
|---|---|---|
/xrpc/io.atcr.hold.initiateUpload |
POST | Start multipart upload |
/xrpc/io.atcr.hold.getPartUploadUrl |
POST | Get presigned S3 URL for a part; the client PUTs the part bytes directly to S3 |
/xrpc/io.atcr.hold.completeUpload |
POST | Finalize multipart upload |
/xrpc/io.atcr.hold.abortUpload |
POST | Cancel multipart upload |
/xrpc/io.atcr.hold.notifyManifest |
POST | Notify manifest push (creates layer records + optional Bluesky post) |
ATCR Hold-Specific Endpoints (io.atcr.hold.*)
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/xrpc/io.atcr.hold.initiateUpload |
POST | blob:write | Start multipart upload |
/xrpc/io.atcr.hold.getPartUploadUrl |
POST | blob:write | Get presigned S3 URL for a part; client PUTs bytes directly to S3 |
/xrpc/io.atcr.hold.completeUpload |
POST | blob:write | Finalize multipart upload |
/xrpc/io.atcr.hold.abortUpload |
POST | blob:write | Cancel multipart upload |
/xrpc/io.atcr.hold.notifyManifest |
POST | blob:write | Notify manifest push/pull (creates layer records, increments stats, optional Bluesky post) |
/xrpc/io.atcr.hold.requestCrew |
POST | service token or DPoP | Request crew membership |
/xrpc/io.atcr.hold.exportUserData |
GET | service token | GDPR data export |
/xrpc/io.atcr.hold.deleteUserData |
DELETE | service token | GDPR data deletion (crew, layer, stats records) |
/xrpc/io.atcr.hold.getQuota |
GET | none | Get user quota info |
/xrpc/io.atcr.hold.getLayersForManifest |
GET | none | Get layer records for a manifest AT-URI |
/xrpc/io.atcr.hold.image.getConfig |
GET | none | Get OCI image config record for a manifest digest |
/xrpc/io.atcr.hold.purgeManifest |
POST | inline (service token or DPoP; captain, crew:admin, or manifest owner) | Purge layer/scan/image-config records for a single manifest URI. Called by appview on UI delete; called internally on takedown receipt. Does not delete S3 blobs (GC handles those). |
/xrpc/io.atcr.hold.listTiers |
GET | none | List hold's available tiers with quotas and features (scanOnPush) |
/xrpc/io.atcr.hold.updateCrewTier |
POST | appview token (ES256 JWT; 503 if appview DID not configured) | Update crew member's tier |
/xrpc/io.atcr.hold.subscribeScanJobs |
GET (WebSocket) | shared secret (?secret= or X-Scanner-Secret) |
Scanner job subscription; supports ?cursor= for backfill |
Standard ATProto Endpoints (excluding io.atcr.hold.*)
| Endpoint |
|---|
| /xrpc/_health |
| /xrpc/com.atproto.server.describeServer |
| /xrpc/com.atproto.repo.describeRepo |
| /xrpc/com.atproto.repo.getRecord |
| /xrpc/com.atproto.repo.listRecords |
| /xrpc/com.atproto.repo.deleteRecord |
| /xrpc/com.atproto.repo.uploadBlob |
| /xrpc/com.atproto.sync.listBlobs |
| /xrpc/com.atproto.sync.listRepos |
| /xrpc/com.atproto.sync.getRecord |
| /xrpc/com.atproto.sync.getRepo |
| /xrpc/com.atproto.sync.getRepoStatus |
| /xrpc/com.atproto.sync.getLatestCommit |
| /xrpc/com.atproto.sync.getBlob |
| /xrpc/com.atproto.sync.subscribeRepos |
| /xrpc/com.atproto.identity.resolveHandle |
| /xrpc/app.bsky.actor.getProfile |
| /xrpc/app.bsky.actor.getProfiles |
| /.well-known/did.json |
| /.well-known/atproto-did |
See Also
- DIRECT_HOLD_ACCESS.md - How to call hold endpoints directly without AppView (app passwords, curl examples)
- BYOS.md - Bring Your Own Storage architecture
- OAUTH.md - OAuth + DPoP authentication details