A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
atcr.io
docker
container
atproto
go
Hold Service Endpoint Testing Guide#
Quick Reference#
Your hold service: http://172.28.0.3:8080
Default DID format for local testing: did:web:172.28.0.3%3A8080 (URL-encoded did:web:172.28.0.3:8080)
Individual cURL Commands#
1. List Repositories#
curl -s "http://172.28.0.3:8080/xrpc/com.atproto.sync.listRepos" | jq .
Expected response:
{
"repos": [
{
"did": "did:web:172.28.0.3%3A8080",
"head": "...",
"rev": "..."
}
]
}
2. Describe Repository#
curl -s "http://172.28.0.3:8080/xrpc/com.atproto.repo.describeRepo?repo=did:web:172.28.0.3%3A8080" | jq .
Expected response:
{
"did": "did:web:172.28.0.3%3A8080",
"handle": "172.28.0.3:8080",
"didDoc": {...},
"collections": ["io.atcr.hold.captain", "io.atcr.hold.crew"]
}
3. Get Repository (CAR file)#
# Download entire repo as CAR file
curl -s "http://172.28.0.3:8080/xrpc/com.atproto.sync.getRepo?did=did:web:172.28.0.3%3A8080" -o repo.car
# Get repo diff since revision
curl -s "http://172.28.0.3:8080/xrpc/com.atproto.sync.getRepo?did=did:web:172.28.0.3%3A8080&since=abc123" -o repo-diff.car
Expected response: Binary CAR (Content Addressable aRchive) file
4. List Captain Records#
curl -s "http://172.28.0.3:8080/xrpc/com.atproto.repo.listRecords?repo=did:web:172.28.0.3%3A8080&collection=io.atcr.hold.captain" | jq .
Expected response:
{
"records": [
{
"uri": "at://did:web:172.28.0.3%3A8080/io.atcr.hold.captain/self",
"cid": "...",
"value": {
"$type": "io.atcr.hold.captain",
"allowAllCrew": true,
"public": false,
"createdAt": "2025-10-22T..."
}
}
]
}
5. List Crew Records#
curl -s "http://172.28.0.3:8080/xrpc/com.atproto.repo.listRecords?repo=did:web:172.28.0.3%3A8080&collection=io.atcr.hold.crew" | jq .
Expected response:
{
"records": [
{
"uri": "at://did:web:172.28.0.3%3A8080/io.atcr.hold.crew/{rkey}",
"cid": "...",
"value": {
"$type": "io.atcr.hold.crew",
"did": "did:plc:...",
"permissions": ["blob:read", "blob:write"],
"createdAt": "2025-10-22T..."
}
}
]
}
6. Get Specific Record#
curl -s "http://172.28.0.3:8080/xrpc/com.atproto.repo.getRecord?repo=did:web:172.28.0.3%3A8080&collection=io.atcr.hold.captain&rkey=self" | jq .
7. Get Blob#
# Replace with actual CID from your hold
curl -s "http://172.28.0.3:8080/xrpc/com.atproto.sync.getBlob?did=did:web:172.28.0.3%3A8080&cid=bafyreiabc123..." | jq .
Expected response (for OCI blobs):
{
"url": "https://s3.amazonaws.com/bucket/path?presigned-params...",
"expiresAt": "2025-10-22T12:15:00Z"
}
8. Subscribe to Repository Events (WebSocket)#
Using websocat (recommended):
# Install: cargo install websocat
websocat "ws://172.28.0.3:8080/xrpc/com.atproto.sync.subscribeRepos"
Using wscat:
# Install: npm install -g wscat
wscat -c "ws://172.28.0.3:8080/xrpc/com.atproto.sync.subscribeRepos"
Using curl (HTTP upgrade - may not work with all servers):
curl -i -N \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: $(echo -n "test" | base64)" \
"http://172.28.0.3:8080/xrpc/com.atproto.sync.subscribeRepos"
Expected response: Stream of CBOR-encoded events (commits, identities, handles, etc.)
DID Resolution#
Get DID Document#
curl -s "http://172.28.0.3:8080/.well-known/did.json" | jq .
Expected response:
{
"@context": ["https://www.w3.org/ns/did/v1"],
"id": "did:web:172.28.0.3%3A8080",
"service": [
{
"id": "#atproto_pds",
"type": "AtprotoPersonalDataServer",
"serviceEndpoint": "http://172.28.0.3:8080"
}
]
}
Get DID from Handle#
curl -s "http://172.28.0.3:8080/.well-known/atproto-did"
Expected response: Plain text DID
did:web:172.28.0.3%3A8080
Running the Test Script#
# Default (uses 172.28.0.3:8080)
./test-hold-endpoints.sh
# Custom hold URL
./test-hold-endpoints.sh "http://localhost:8080"
# Custom hold URL and DID
./test-hold-endpoints.sh "http://localhost:8080" "did:web:localhost%3A8080"
Troubleshooting#
"Connection refused"#
- Ensure hold service is running:
docker psor check process - Verify IP address:
docker inspect <container> | grep IPAddress
"Empty response" or "404 Not Found"#
- Check hold service logs for errors
- Verify DID format (use URL-encoded version with
%3Afor:) - Ensure hold has been initialized (should have captain record)
WebSocket connection fails#
- Install websocat:
cargo install websocat - Or install wscat:
npm install -g wscat - WebSocket endpoints only work with proper WS clients, not regular curl
"No records found"#
- Captain record created on hold startup if
HOLD_OWNERis set - Crew records created when users call
io.atcr.hold.requestCrew - Blobs only exist after pushing container images
Next Steps#
After verifying these endpoints work:
- Test OCI upload endpoints (requires authentication)
- Push a real container image to create blob data
- Test blob retrieval with real CIDs
- Monitor WebSocket events during pushes