Bluesky AT Protocol — SKILL.md
Raw skill file that agents receive when using this skill
---
name: "Bluesky AT Protocol"
description: "Skill for Bluesky AT Protocol — auto-generated from documentation"
version: "1.0.0"
author: "skynet"
category: "social"
agents: ["claude-code", "codex", "gemini"]
tags: ["bluesky-api", "social", "auto-generated"]
---
# Bluesky AT Protocol
---
name: Bluesky AT Protocol
description: Use when working with Bluesky's AT Protocol for building decentralized social applications, managing repositories, or interacting with the network
metadata:
author: skynet
version: 1.0.0
category: social
---
# Bluesky AT Protocol
## Overview
AT Protocol is Bluesky's federated social networking protocol that enables decentralized identity, data portability, and algorithmic choice. Use this for building social apps, managing user data, or creating custom feeds.
## Core Concepts
### Identity & Authentication
- **DID (Decentralized Identifier)**: Unique user identifier
- **Handle**: Human-readable username (e.g., @alice.bsky.social)
- **Repository**: User's data storage containing records
- **Lexicons**: Schema definitions for data structures
### Key Services
- **PDS (Personal Data Server)**: Stores user repositories
- **BGS (Big Graph Service)**: Aggregates network data
- **AppView**: Provides application-specific views
- **Feed Generator**: Creates custom algorithmic feeds
## CLI Setup & Authentication
### Install AT Protocol CLI
```bash
# Install ATP CLI
npm install -g @atproto/dev-env
# Alternative: Clone and build from source
git clone https://github.com/bluesky-social/atproto.git
cd atproto
npm install
npm run build
```
### Create Session
```bash
# Login to get access token
curl -X POST https://bsky.social/xrpc/com.atproto.server.createSession \
-H "Content-Type: application/json" \
-d '{
"identifier": "your-handle.bsky.social",
"password": "your-app-password"
}'
# Save response for authentication
export ATP_ACCESS_TOKEN="your-access-token"
export ATP_DID="your-did"
```
## Common Operations
### Create Post
```bash
# Basic post
curl -X POST https://bsky.social/xrpc/com.atproto.repo.createRecord \
-H "Authorization: Bearer $ATP_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"repo": "'$ATP_DID'",
"collection": "app.bsky.feed.post",
"record": {
"$type": "app.bsky.feed.post",
"text": "Hello AT Protocol!",
"createdAt": "'$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)'"
}
}'
# Post with mention
curl -X POST https://bsky.social/xrpc/com.atproto.repo.createRecord \
-H "Authorization: Bearer $ATP_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"repo": "'$ATP_DID'",
"collection": "app.bsky.feed.post",
"record": {
"$type": "app.bsky.feed.post",
"text": "Hello @alice.bsky.social!",
"facets": [{
"index": {"byteStart": 6, "byteEnd": 23},
"features": [{
"$type": "app.bsky.richtext.facet#mention",
"did": "did:plc:mentioned-user-did"
}]
}],
"createdAt": "'$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)'"
}
}'
```
### Retrieve Records
```bash
# Get user profile
curl "https://bsky.social/xrpc/app.bsky.actor.getProfile?actor=$ATP_DID" \
-H "Authorization: Bearer $ATP_ACCESS_TOKEN"
# Get timeline
curl "https://bsky.social/xrpc/app.bsky.feed.getTimeline?limit=20" \
-H "Authorization: Bearer $ATP_ACCESS_TOKEN"
# List records in collection
curl "https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=$ATP_DID&collection=app.bsky.feed.post" \
-H "Authorization: Bearer $ATP_ACCESS_TOKEN"
```
### Follow/Unfollow Users
```bash
# Follow a user
curl -X POST https://bsky.social/xrpc/com.atproto.repo.createRecord \
-H "Authorization: Bearer $ATP_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"repo": "'$ATP_DID'",
"collection": "app.bsky.graph.follow",
"record": {
"$type": "app.bsky.graph.follow",
"subject": "did:plc:target-user-did",
"createdAt": "'$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)'"
}
}'
# Unfollow (delete follow record)
curl -X POST https://bsky.social/xrpc/com.atproto.repo.deleteRecord \
-H "Authorization: Bearer $ATP_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"repo": "'$ATP_DID'",
"collection": "app.bsky.graph.follow",
"rkey": "follow-record-key"
}'
```
## Decision Trees
### Choosing Data Storage
```
Need to store user data?
├── Personal posts/profile → Use PDS repository
├── Application metadata → Use custom lexicon
├── Large media files → Use blob storage + references
└── Cross-user aggregation → Query BGS/AppView
```
### Authentication Method
```
Authentication type needed?
├── User login → createSession with handle/password
├── Service-to-service → Use service JWT
├── Public data only → No auth required
└── Custom PDS → Implement own auth flow
```
### Record Operations
```
What record operation?
├── Create new → com.atproto.repo.createRecord
├── Update existing → com.atproto.repo.putRecord
├── Delete → com.atproto.repo.deleteRecord
└── Batch operations → com.atproto.repo.applyWrites
```
## Development Workflows
### Local PDS Setup
```bash
# Clone PDS
git clone https://github.com/bluesky-social/pds.git
cd pds
# Configure environment
cp .env.example .env
# Edit .env with your settings
# Start PDS
npm install
npm run build
npm start
# Create account on local PDS
curl -X POST http://localhost:2583/xrpc/com.atproto.server.createAccount \
-H "Content-Type: application/json" \
-d '{
"handle": "alice.localhost",
"email": "alice@example.com",
"password": "secure-password"
}'
```
### Custom Feed Generator
```bash
# Initialize feed generator
mkdir my-feed-generator
cd my-feed-generator
npm init -y
# Install dependencies
npm install @atproto/api @atproto/lexicon
# Basic feed generator structure
cat > index.js << 'EOF'
const { BskyAgent } = require('@atproto/api')
const agent = new BskyAgent({
service: 'https://bsky.social'
})
async function generateFeed() {
// Custom feed algorithm
const posts = await agent.app.bsky.feed.getTimeline({
limit: 50
})
// Filter and rank posts
return posts.data.feed.filter(post =>
post.post.record.text.includes('#custom')
)
}
module.exports = { generateFeed }
EOF
```
## Troubleshooting
### Common Errors
**Error: "Invalid identifier"**
```bash
# Check handle resolution
curl "https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=your-handle.bsky.social"
# Verify DID format
echo "DID should start with 'did:plc:' or 'did:web:'"
```
**Error: "ExpiredToken"**
```bash
# Refresh session
curl -X POST https://bsky.social/xrpc/com.atproto.server.refreshSession \
-H "Authorization: Bearer $ATP_REFRESH_TOKEN"
# Create new session if refresh fails
unset ATP_ACCESS_TOKEN
# Re-run createSession
```
**Error: "InvalidSwap" during record updates**
```bash
# Get current record first
curl "https://bsky.social/xrpc/com.atproto.repo.getRecord?repo=$ATP_DID&collection=app.bsky.feed.post&rkey=record-key" \
-H "Authorization: Bearer $ATP_ACCESS_TOKEN"
# Use returned CID as swapRecord parameter
curl -X POST https://bsky.social/xrpc/com.atproto.repo.putRecord \
-H "Authorization: Bearer $ATP_ACCESS_TOKEN" \
-d '{
"repo": "'$ATP_DID'",
"collection": "app.bsky.feed.post",
"rkey": "record-key",
"swapRecord": "current-record-cid",
"record": {...}
}'
```
**Error: "RateLimitExceeded"**
```bash
# Check rate limit headers
curl -I "https://bsky.social/xrpc/app.bsky.feed.getTimeline"
# Implement exponential backoff
sleep_time=1
for i in {1..5}; do
if curl -f "your-request"; then
break
else
sleep $sleep_time
sleep_time=$((sleep_time * 2))
fi
done
```
### Debug Mode
```bash
# Enable verbose logging
export ATP_DEBUG=true
export NODE_ENV=development
# Test connection
curl -v "https://bsky.social/xrpc/com.atproto.server.describeServer"
```
## Security Best Practices
- Use app passwords instead of main account password
- Store tokens securely, never in code repositories
- Implement proper session refresh logic
- Validate all user inputs before creating records
- Use HTTPS for all API calls
- Regularly rotate authentication credentials
curl -s https://skills.skynet.ceo/api/skills/bluesky-api/skill.md