can you please provide a createRecord call with the optional offprint specifics?
Reading & writing Standard.site documents over AT Protocol (curl)
Reading & writing Standard.site documents over AT Protocol (curl) #
A "Standard.site document" (a blog post / article on Offprint, Unthread, etc.) is just a
record in the site.standard.document collection of the author's repo.
- Reading is unauthenticated. You hit
com.atproto.repo.listRecords/getRecordon the author's PDS (not bsky.social, not the appview). - Writing is the same
com.atproto.repo.createRecordcall you already use forapp.bsky.feed.post— same auth, differentcollectionandrecord.
Lexicon: at://did:plc:re3ebnp5v7ffagz6rb6xfei4/com.atproto.lexicon.schema/site.standard.document
Required fields: site, title, publishedAt.
0. Resolve the author's PDS (records live on their PDS) #
HANDLE="bunniesin.space"
# handle -> DID
DID=$(curl -s "https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=$HANDLE" \
| jq -r .did)
# DID -> PDS endpoint (works for did:plc and did:web)
PDS=$(curl -s "https://plc.directory/$DID" \
| jq -r '.service[] | select(.id=="#atproto_pds") | .serviceEndpoint')
echo "$DID @ $PDS"
# did:plc:gijpvbkdbr56kazbdjhfvb3d @ https://eurosky.social
1. List a repo's documents (no auth) #
curl -s "$PDS/xrpc/com.atproto.repo.listRecords?repo=$DID&collection=site.standard.document&limit=10" \
| jq '.records[] | {uri, title: .value.title, publishedAt: .value.publishedAt}'
2. Fetch one document by rkey (no auth) #
RKEY="3moef5ysngd23" # last segment of the at:// URI
curl -s "$PDS/xrpc/com.atproto.repo.getRecord?repo=$DID&collection=site.standard.document&rkey=$RKEY" \
| jq .
The body lives in value. Two fields matter:
value.textContent— plaintext of the whole doc. Use this if you just want the text.value.content— the rich block tree, an open union. Each item has a$type(Offprint emitsapp.offprint.block.*; other apps emit their own). Render the ones you know, fall back totextContentfor the rest.
3. Create a document (auth required) #
Auth is identical to posting to Bluesky. App-password example:
# Get a session (or reuse the access token you already have from OAuth)
PDS="https://your-pds.example" # the AUTHENTICATED user's PDS
RESP=$(curl -s "$PDS/xrpc/com.atproto.server.createSession" \
-H "Content-Type: application/json" \
-d '{"identifier":"you.bsky.social","password":"xxxx-xxxx-xxxx-xxxx"}')
TOKEN=$(echo "$RESP" | jq -r .accessJwt)
DID=$(echo "$RESP" | jq -r .did)
# Create the site.standard.document record
curl -s "$PDS/xrpc/com.atproto.repo.createRecord" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @- <<JSON | jq .
{
"repo": "$DID",
"collection": "site.standard.document",
"record": {
"\$type": "site.standard.document",
"site": "https://yourblog.example",
"title": "Hello from curl",
"description": "A document created straight over XRPC.",
"path": "/hello-from-curl",
"publishedAt": "2026-06-26T12:00:00Z",
"textContent": "This is the plaintext body of the document."
}
}
JSON
Notes on the fields:
site(required, uri) — the publication this belongs to. Anat://URI to asite.standard.publicationrecord, or anhttps://URL for a loose document.publishedAt(required, datetime) — ISO 8601.path— combined with the site URL to build the canonical URL; lead with/.contentis optional and open; omit it andtextContentalone is a valid, portable document any reader can display.createRecordmints the rkey (a TID) for you; pass"rkey"yourself to set it.
Offprint specifics (optional) #
Offprint pairs each site.standard.document with an app.offprint.document.article
record that holds a com.atproto.repo.strongRef to the document (same pattern for
app.offprint.publication -> site.standard.publication). The portable document is
the site.standard.* one; the app.offprint.* records are Offprint's app-side index.
If you only care about the content, read site.standard.document and ignore the rest.