Monorepo for Aesthetic.Computer aesthetic.computer

User Pages - API Examples#

Real examples of ATProto API responses used by the user pages.

1. Resolve Handle to DID#

Request#

GET https://at.aesthetic.computer/xrpc/com.atproto.identity.resolveHandle?handle=fifi.at.aesthetic.computer

Response#

{
  "did": "did:plc:xyz123abc456def789ghi012jkl345"
}

2. List Painting Records#

Request#

GET https://at.aesthetic.computer/xrpc/com.atproto.repo.listRecords?repo=did:plc:xyz123...&collection=computer.aesthetic.painting&limit=100

Response#

{
  "records": [
    {
      "uri": "at://did:plc:xyz123.../computer.aesthetic.painting/3jzx7k2m4n5",
      "cid": "bafyreib...",
      "value": {
        "$type": "computer.aesthetic.painting",
        "slug": "2023.10.20.15.30.45",
        "code": "abc123",
        "createdAt": "2023-10-20T15:30:45.123Z",
        "image": {
          "$type": "blob",
          "ref": {
            "$link": "bafkreiabcd..."
          },
          "mimeType": "image/png",
          "size": 45678
        },
        "ref": "65f3d2c1a8b9e4f5a6b7c8d9"
      }
    },
    {
      "uri": "at://did:plc:xyz123.../computer.aesthetic.painting/2hwy6j1l3m4",
      "cid": "bafyreic...",
      "value": {
        "$type": "computer.aesthetic.painting",
        "slug": "2023.10.19.12.15.30",
        "code": "def456",
        "createdAt": "2023-10-19T12:15:30.456Z",
        "image": {
          "$type": "blob",
          "ref": {
            "$link": "bafkreixyz..."
          },
          "mimeType": "image/png",
          "size": 38901
        },
        "ref": "65f3d2c1a8b9e4f5a6b7c8d8"
      }
    }
  ],
  "cursor": "3jzx7k2m4n5"
}

3. List Mood Records#

Request#

GET https://at.aesthetic.computer/xrpc/com.atproto.repo.listRecords?repo=did:plc:xyz123...&collection=computer.aesthetic.mood&limit=100

Response#

{
  "records": [
    {
      "uri": "at://did:plc:xyz123.../computer.aesthetic.mood/5pzn8r3q4t6",
      "cid": "bafyreid...",
      "value": {
        "$type": "computer.aesthetic.mood",
        "mood": "Feeling creative today! Just finished a new piece.",
        "when": "2023-10-20T14:25:15.789Z",
        "ref": "65f3d2c1a8b9e4f5a6b7c8d7"
      }
    },
    {
      "uri": "at://did:plc:xyz123.../computer.aesthetic.mood/4nyi7q2p3s5",
      "cid": "bafyreie...",
      "value": {
        "$type": "computer.aesthetic.mood",
        "mood": "Working on something experimental...",
        "when": "2023-10-19T09:45:30.123Z",
        "ref": "65f3d2c1a8b9e4f5a6b7c8d6"
      }
    }
  ],
  "cursor": null
}

4. Get Blob (Image)#

Request#

GET https://at.aesthetic.computer/xrpc/com.atproto.sync.getBlob?did=did:plc:xyz123...&cid=bafkreiabcd...

Response#

Binary image data (PNG)

Record URI Structure#

ATProto URIs follow this pattern:

at://[DID]/[COLLECTION]/[RKEY]

Examples:

  • at://did:plc:xyz123.../computer.aesthetic.painting/3jzx7k2m4n5
  • at://did:plc:xyz123.../computer.aesthetic.mood/5pzn8r3q4t6

Components:

  • DID: Decentralized identifier for the user
  • Collection: Lexicon name (e.g., computer.aesthetic.painting)
  • RKEY: Record key (unique ID within collection)

Field Descriptions#

Painting Record Fields#

Field Type Description
$type string Always computer.aesthetic.painting
slug string Timestamp-based painting ID
code string Short alphanumeric code (e.g., abc123)
createdAt string ISO 8601 timestamp
image blob Reference to image data
ref string MongoDB ObjectId reference

Mood Record Fields#

Field Type Description
$type string Always computer.aesthetic.mood
mood string The mood text content
when string ISO 8601 timestamp
ref string MongoDB ObjectId reference

Blob Fields#

Field Type Description
$type string Always blob
ref.$link string CID (Content Identifier)
mimeType string MIME type (e.g., image/png)
size number Size in bytes

Error Responses#

Handle Not Found#

{
  "error": "InvalidRequest",
  "message": "Unable to resolve handle: fifi.at.aesthetic.computer"
}

Status: 400

Collection Not Found#

{
  "error": "InvalidRequest",
  "message": "Collection not found: computer.aesthetic.painting"
}

Status: 400

DID Not Found#

{
  "error": "InvalidRequest",
  "message": "Could not find repo: did:plc:xyz123..."
}

Status: 400

Pagination#

When there are more than 100 records, use the cursor:

Request with Cursor#

GET https://at.aesthetic.computer/xrpc/com.atproto.repo.listRecords?repo=did:plc:xyz123...&collection=computer.aesthetic.painting&limit=100&cursor=3jzx7k2m4n5

Response#

{
  "records": [
    // Next 100 records...
  ],
  "cursor": "2gwx6i1k2m3"  // Or null if no more records
}

JavaScript Examples#

Fetch Handle DID#

const PDS_URL = 'https://at.aesthetic.computer';

async function resolveDID(handle) {
  const url = `${PDS_URL}/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}`;
  const response = await fetch(url);
  const data = await response.json();
  return data.did;
}

// Usage
const did = await resolveDID('fifi.at.aesthetic.computer');
console.log(did); // did:plc:xyz123...

List All Records (with Pagination)#

async function listRecords(did, collection) {
  const records = [];
  let cursor = undefined;

  do {
    const url = new URL(`${PDS_URL}/xrpc/com.atproto.repo.listRecords`);
    url.searchParams.append('repo', did);
    url.searchParams.append('collection', collection);
    url.searchParams.append('limit', 100);
    if (cursor) url.searchParams.append('cursor', cursor);

    const response = await fetch(url);
    const data = await response.json();
    
    records.push(...data.records);
    cursor = data.cursor;
  } while (cursor);

  return records;
}

// Usage
const paintings = await listRecords(did, 'computer.aesthetic.painting');
console.log(`Found ${paintings.length} paintings`);

Get Image URL#

function getImageUrl(did, blobCid) {
  return `${PDS_URL}/xrpc/com.atproto.sync.getBlob?did=${did}&cid=${blobCid}`;
}

// Usage
const painting = records[0];
const did = painting.uri.split('/')[2];
const cid = painting.value.image.ref.$link;
const imageUrl = getImageUrl(did, cid);

// Use in img tag
document.querySelector('img').src = imageUrl;

Extract Record Key (RKEY)#

function getRkey(uri) {
  return uri.split('/').pop();
}

// Usage
const uri = 'at://did:plc:xyz123.../computer.aesthetic.painting/3jzx7k2m4n5';
const rkey = getRkey(uri); // '3jzx7k2m4n5'

Testing with cURL#

# 1. Resolve handle
curl "https://at.aesthetic.computer/xrpc/com.atproto.identity.resolveHandle?handle=fifi.at.aesthetic.computer"

# 2. List paintings (replace DID)
curl "https://at.aesthetic.computer/xrpc/com.atproto.repo.listRecords?repo=did:plc:xyz123...&collection=computer.aesthetic.painting&limit=5"

# 3. Get image (replace DID and CID)
curl "https://at.aesthetic.computer/xrpc/com.atproto.sync.getBlob?did=did:plc:xyz123...&cid=bafkreiabcd..." --output image.png

# 4. List moods
curl "https://at.aesthetic.computer/xrpc/com.atproto.repo.listRecords?repo=did:plc:xyz123...&collection=computer.aesthetic.mood&limit=5"

Rate Limiting#

The PDS does not currently enforce strict rate limits, but:

  • Recommended: Max 100 requests per minute per client
  • Pagination: Use reasonable limits (100-500)
  • Caching: Cache resolved DIDs client-side

CORS#

The PDS allows CORS requests from any origin:

Access-Control-Allow-Origin: *

This enables the user pages to work purely client-side.

Security Notes#

  1. No Authentication Required - All data is public
  2. Read-Only - XRPC endpoints used are read-only
  3. No Personal Data - Only public ATProto records
  4. Client-Side Only - No server-side secrets exposed

Last Updated: 2025-10-20