Skip to main content

Records

Experimental

This API is experimental and will change. See the Permissioned Spaces overview for context.

Space records are stored separately from public AT Protocol records. They follow the same URI pattern but use the ats:// scheme and include the space identity:

ats:// did:plc:abcdefghijklmnop1234567890 / com.example.forum / main / did:plc:author / com.example.forum.post / abcdefghijklmnop1234567890
└── space DID ───────────────────┘ └── space type ─┘ └── skey ─┘ └── author ──┘ └── collection ──────┘ └── rkey ────────────────┘

Creating a record

Requires write membership in the space. The rkey is auto-generated using a TID.

curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.createRecord' \
-H 'X-Client-Key: hvc_...' \
-H 'Authorization: DPoP <token>' \
-H 'DPoP: <proof>' \
-H 'Content-Type: application/json' \
-d '{
"space": "ats://did:plc:abc123/com.example.forum/main",
"collection": "com.example.forum.post",
"record": {
"$type": "com.example.forum.post",
"text": "Hello from the forum!",
"createdAt": "2026-05-09T12:00:00Z"
}
}'

Input:

FieldTypeRequiredDescription
spacestringYesThe space to write into
collectionstring (NSID)YesThe record collection
recordobjectYesThe record data

Response (201):

{
"uri": "ats://did:plc:abc123/com.example.forum/main/did:plc:author/com.example.forum.post/3l2tkbx7225co",
"cid": "bafyrei..."
}

createRecord always inserts a new record. If a record with the generated URI already exists, it returns 409 Conflict.

Writing a record (put)

Requires write membership in the space.

curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.putRecord' \
-H 'X-Client-Key: hvc_...' \
-H 'Authorization: DPoP <token>' \
-H 'DPoP: <proof>' \
-H 'Content-Type: application/json' \
-d '{
"space": "ats://did:plc:abc123/com.example.forum/main",
"collection": "com.example.forum.post",
"rkey": "3k2abc",
"record": {
"$type": "com.example.forum.post",
"text": "Hello from the forum!",
"createdAt": "2026-05-09T12:00:00Z"
}
}'

Input:

FieldTypeRequiredDescription
spacestringYesThe space to write into
collectionstring (NSID)YesThe record collection
rkeystringYesThe record key
recordobjectYesThe record data
swapRecordstringNoExpected CID of the existing record (for optimistic concurrency)

Response (201):

{
"uri": "ats://did:plc:abc123/com.example.forum/main/did:plc:author/com.example.forum.post/3k2abc",
"cid": "bafyrei..."
}

The author DID is taken from the authenticated user. You can only write records as yourself, so the URI's author component will always be your DID.

putRecord performs an upsert: if a record with the same collection + rkey already exists for this author in this space, it's overwritten. Use swapRecord to prevent unintended overwrites (see Optimistic concurrency below).

Getting a record

Requires read membership (or a valid space credential).

curl 'https://happyview.example.com/xrpc/dev.happyview.space.getRecord?space=ats://did:plc:abc123/com.example.forum/main&collection=com.example.forum.post&rkey=3k2abc' \
-H 'X-Client-Key: hvc_...' \
-H 'Authorization: DPoP <token>' \
-H 'DPoP: <proof>'

Response:

{
"uri": "ats://did:plc:abc123/com.example.forum/main/did:plc:author/com.example.forum.post/3k2abc",
"cid": "bafyrei...",
"value": {
"$type": "com.example.forum.post",
"text": "Hello from the forum!",
"createdAt": "2026-05-09T12:00:00Z"
}
}

Listing records

curl 'https://happyview.example.com/xrpc/dev.happyview.space.listRecords?space=ats://did:plc:abc123/com.example.forum/main&collection=com.example.forum.post&limit=20' \
-H 'X-Client-Key: hvc_...' \
-H 'Authorization: DPoP <token>' \
-H 'DPoP: <proof>'

Parameters:

FieldTypeRequiredDefaultDescription
spacestringYesThe space to list from
repostringNoFilter by author DID
collectionstringNoFilter by collection NSID
limitintegerNo50Max records to return (1-100)
cursorstringNoPagination cursor
reversebooleanNofalseReverse sort order (oldest first)

Response:

{
"records": [
{
"collection": "com.example.forum.post",
"rkey": "3k2abc",
"cid": "bafyrei..."
}
],
"cursor": "2026-05-09T12:00:00Z"
}

Deleting a record

You can only delete your own records. Requires write membership.

curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.deleteRecord' \
-H 'X-Client-Key: hvc_...' \
-H 'Authorization: DPoP <token>' \
-H 'DPoP: <proof>' \
-H 'Content-Type: application/json' \
-d '{
"space": "ats://did:plc:abc123/com.example.forum/main",
"collection": "com.example.forum.post",
"rkey": "3k2abc"
}'

Input:

FieldTypeRequiredDescription
spacestringYesThe space containing the record
collectionstring (NSID)YesThe record collection
rkeystringYesThe record key
swapRecordstringNoExpected CID of the existing record (for optimistic concurrency)

Attempting to delete another user's record returns 403 Forbidden.

Batch writes (applyWrites)

applyWrites performs multiple create, update, and delete operations in a single request. Requires write membership.

curl -X POST 'https://happyview.example.com/xrpc/dev.happyview.space.applyWrites' \
-H 'X-Client-Key: hvc_...' \
-H 'Authorization: DPoP <token>' \
-H 'DPoP: <proof>' \
-H 'Content-Type: application/json' \
-d '{
"space": "ats://did:plc:abc123/com.example.forum/main",
"writes": [
{
"action": "create",
"collection": "com.example.forum.post",
"value": { "$type": "com.example.forum.post", "text": "First post" }
},
{
"action": "update",
"collection": "com.example.forum.post",
"rkey": "3k2abc",
"value": { "$type": "com.example.forum.post", "text": "Edited post" },
"swapRecord": "bafyrei..."
},
{
"action": "delete",
"collection": "com.example.forum.post",
"rkey": "old-post"
}
]
}'

Input:

FieldTypeRequiredDescription
spacestringYesThe space to write into
swapCommitstringNoExpected space revision (for optimistic concurrency)
writesarrayYesList of write operations

Each write operation has an action field:

ActionFieldsDescription
createcollection, value, rkey?Insert a new record. Auto-generates rkey if omitted.
updatecollection, rkey, value, swapRecord?Upsert a record.
deletecollection, rkey, swapRecord?Delete a record.

Response:

{
"results": [
{ "uri": "ats://...", "cid": "bafyrei..." },
{ "uri": "ats://...", "cid": "bafyrei..." },
{}
]
}

Each entry in results corresponds to the write at the same index. Create and update operations return uri and cid; delete operations return an empty object.

Optimistic concurrency

swapRecord and swapCommit provide optimistic concurrency control to prevent lost updates when multiple clients write to the same space.

swapRecord

Pass the swapRecord field on putRecord, deleteRecord, or individual operations within applyWrites. The value is the CID of the record you expect to be replacing. If the record's current CID doesn't match, the operation fails with 409 Conflict.

{
"space": "ats://did:plc:abc123/com.example.forum/main",
"collection": "com.example.forum.post",
"rkey": "3k2abc",
"record": { "text": "updated safely" },
"swapRecord": "bafyrei_old_cid"
}

swapCommit

Pass the swapCommit field on applyWrites to assert the space's current revision. If another client has written to the space since you last read its state, the operation fails with 409 Conflict before any writes are applied.

The space's current revision is available as revision in the space object returned by dev.happyview.space.getSpace.

{
"space": "ats://did:plc:abc123/com.example.forum/main",
"swapCommit": "3l2tkbx7225co",
"writes": [...]
}

Cross-service access

Records can also be read using a space credential instead of direct membership. Pass the credential as a Bearer token:

curl 'https://happyview.example.com/xrpc/dev.happyview.space.getRecord?...' \
-H 'Authorization: Bearer eyJhbGciOiJFUzI1NiIsInR5cCI6InNwYWNlX2NyZWRlbnRpYWwifQ...'

A feed generator or other service that isn't a direct member can use a credential issued by the space owner to read data without joining the space. No DPoP auth is needed — the credential itself authenticates the request.