Documentation Index
Fetch the complete documentation index at: https://mintlify.com/bluesky-social/atproto/llms.txt
Use this file to discover all available pages before exploring further.
@atproto/bsky
TypeScript implementation of the app.bsky Lexicons backing the Bluesky microblogging application. This package provides the AppView service that aggregates data from Personal Data Servers and serves the Bluesky API.
Installation
npm install @atproto/bsky
Overview
The Bluesky AppView is a service that:
- Indexes data from multiple PDSs via the data plane
- Provides the
app.bsky.* Lexicon endpoints
- Implements social graph algorithms (feeds, notifications, suggestions)
- Manages content moderation and labeling
- Handles blob/media serving and transformation
- Provides search functionality
Architecture:
- AppView: Public-facing API service
- Data Plane: Backend indexing and data aggregation
- Hydrator: Enriches data with additional context
- Views: Formats data for API responses
Main Exports
BskyAppView Class
The main AppView server class.
import { BskyAppView, ServerConfig } from '@atproto/bsky'
import { Keypair } from '@atproto/crypto'
// Load configuration
const config = ServerConfig.readEnv()
// Generate signing key
const signingKey = await Keypair.create()
// Create AppView
const appView = BskyAppView.create({
config,
signingKey,
})
// Start server
await appView.start()
Methods
static create(opts: { config: ServerConfig; signingKey: Keypair }): BskyAppView
Creates a new AppView instance.
async start(): Promise<http.Server>
Starts the AppView server and establishes connections to dependencies.
async destroy(): Promise<void>
Gracefully shuts down the server.
ServerConfig Class
Configuration management for the AppView.
import { ServerConfig } from '@atproto/bsky'
// Read from environment variables
const config = ServerConfig.readEnv()
// Access configuration
const port = config.port
const serverDid = config.serverDid
const dataplaneUrls = config.dataplaneUrls
AppContext
Application context with shared services.
import { AppContext } from '@atproto/bsky'
const ctx = appView.ctx
// Available services:
ctx.dataplane // Data plane client
ctx.hydrator // Data hydration
ctx.views // View formatting
ctx.authVerifier // Authentication
ctx.searchAgent // Search service client
ctx.bsyncClient // Sync service client
Configuration
ServerConfigValues
Complete configuration interface:
interface ServerConfigValues {
// Service
version?: string
debugMode?: boolean
port?: number
publicUrl?: string
serverDid: string
alternateAudienceDids: string[]
entrywayJwtPublicKeyHex?: string
// External services
etcdHosts: string[]
dataplaneUrls: string[]
dataplaneUrlsEtcdKeyPrefix?: string
dataplaneHttpVersion?: '1.1' | '2'
dataplaneIgnoreBadTls?: boolean
bsyncUrl: string
bsyncApiKey?: string
searchUrl?: string
suggestionsUrl?: string
topicsUrl?: string
// CDN and media
cdnUrl?: string
videoPlaylistUrlPattern?: string
videoThumbnailUrlPattern?: string
blobRateLimitBypassKey?: string
blobRateLimitBypassHostname?: string
// Identity
didPlcUrl: string
handleResolveNameservers?: string[]
// Moderation
modServiceDid: string
adminPasswords: string[]
labelsFromIssuerDids?: string[]
// Threading
bigThreadUris: Set<string>
maxThreadDepth?: number
maxThreadParents: number
threadTagsHide: Set<string>
threadTagsBumpDown: Set<string>
// Feature flags
eventProxyTrackingEndpoint?: string
growthBookApiHost?: string
growthBookClientKey?: string
clientCheckEmailConfirmed?: boolean
topicsEnabled?: boolean
// HTTP proxy
disableSsrfProtection?: boolean
proxyAllowHTTP2?: boolean
proxyHeadersTimeout?: number
proxyBodyTimeout?: number
proxyMaxResponseSize?: number
}
Environment Variables
Service Configuration
BSKY_VERSION=0.0.1
BSKY_PORT=2584
BSKY_PUBLIC_URL=https://api.bsky.app
BSKY_SERVER_DID=did:web:api.bsky.app
Data Plane
# Static URLs
BSKY_DATAPLANE_URLS=http://dataplane-1:2582,http://dataplane-2:2582
# Or dynamic discovery via etcd
BSKY_ETCD_HOSTS=http://etcd-1:2379,http://etcd-2:2379
BSKY_DATAPLANE_URLS_ETCD_KEY_PREFIX=/bsky/dataplane/urls
# HTTP version
BSKY_DATAPLANE_HTTP_VERSION=2
BSKY_DATAPLANE_IGNORE_BAD_TLS=false
Sync Service (Bsync)
BSKY_BSYNC_URL=http://bsync:2586
BSKY_BSYNC_API_KEY=secret-api-key
BSKY_BSYNC_HTTP_VERSION=2
External Services
# Search
BSKY_SEARCH_URL=http://search:2585
# Suggestions
BSKY_SUGGESTIONS_URL=http://suggestions:2587
BSKY_SUGGESTIONS_API_KEY=suggestions-api-key
# Topics
BSKY_TOPICS_URL=http://topics:2588
BSKY_TOPICS_API_KEY=topics-api-key
BSKY_CDN_URL=https://cdn.bsky.app
BSKY_VIDEO_PLAYLIST_URL_PATTERN=https://video.bsky.app/watch/%s/%s/playlist.m3u8
BSKY_VIDEO_THUMBNAIL_URL_PATTERN=https://video.bsky.app/watch/%s/%s/thumbnail.jpg
BSKY_BLOB_CACHE_LOC=/tmp/blob-cache
Identity
BSKY_DID_PLC_URL=https://plc.directory
BSKY_HANDLE_RESOLVE_NAMESERVERS=8.8.8.8,1.1.1.1
Moderation
MOD_SERVICE_DID=did:plc:moderation
BSKY_ADMIN_PASSWORDS=admin-password-1,admin-password-2
BSKY_LABELS_FROM_ISSUER_DIDS=did:plc:labeler1,did:plc:labeler2
Threading
BSKY_BIG_THREAD_URIS=at://did:plc:abc/app.bsky.feed.post/123
BSKY_BIG_THREAD_DEPTH=20
BSKY_MAX_THREAD_DEPTH=1000
BSKY_MAX_THREAD_PARENTS=50
BSKY_THREAD_TAGS_HIDE=spam,sensitive
BSKY_THREAD_TAGS_BUMP_DOWN=low-quality
Feature Flags
BSKY_CLIENT_CHECK_EMAIL_CONFIRMED=true
BSKY_TOPICS_ENABLED=true
BSKY_EVENT_PROXY_TRACKING_ENDPOINT=http://analytics:9000
GrowthBook (A/B Testing)
BSKY_GROWTHBOOK_API_HOST=https://growthbook-api.example.com
BSKY_GROWTHBOOK_CLIENT_KEY=sdk-abc123
Setup Example
import { BskyAppView, ServerConfig } from '@atproto/bsky'
import { Keypair } from '@atproto/crypto'
async function main() {
// Load configuration from environment
const config = ServerConfig.readEnv()
// Create signing keypair
const signingKeyHex = process.env.BSKY_SIGNING_KEY_HEX
const signingKey = signingKeyHex
? await Keypair.import(signingKeyHex)
: await Keypair.create()
// Create AppView
const appView = BskyAppView.create({
config,
signingKey,
})
// Start server
const server = await appView.start()
const address = server.address()
const port = typeof address === 'string' ? address : address?.port
console.log(`Bluesky AppView listening on port ${port}`)
// Graceful shutdown
const shutdown = async () => {
console.log('Shutting down AppView...')
await appView.destroy()
process.exit(0)
}
process.on('SIGTERM', shutdown)
process.on('SIGINT', shutdown)
}
main().catch((err) => {
console.error('Fatal error:', err)
process.exit(1)
})
Data Plane Client
The AppView communicates with the data plane for indexed data:
import { createDataPlaneClient } from '@atproto/bsky'
const dataplane = createDataPlaneClient(
dataplaneHostList,
{
httpVersion: '2',
rejectUnauthorized: true,
}
)
// Access from context
const dataplane = appView.ctx.dataplane
// Query data
const actor = await dataplane.getActor({ did: 'did:plc:abc123' })
const posts = await dataplane.getTimeline({ did: 'did:plc:abc123' })
Hydrator
The hydrator enriches data with additional context:
import { Hydrator } from '@atproto/bsky'
const hydrator = new Hydrator(
dataplane,
labelsFromIssuerDids,
{
debugFieldAllowedDids,
}
)
// Hydrate actor profiles
const actors = await hydrator.hydrateActors(
['did:plc:abc123', 'did:plc:def456']
)
// Hydrate posts
const posts = await hydrator.hydratePosts(
['at://did:plc:abc/app.bsky.feed.post/123']
)
Views
Views format hydrated data for API responses:
import { Views } from '@atproto/bsky'
const views = new Views({
imgUriBuilder,
videoUriBuilder,
indexedAtEpoch,
threadTagsBumpDown,
threadTagsHide,
})
// Format actor profile
const profileView = views.profile(actorState, viewerDid)
// Format post
const postView = views.post(postState, viewerDid)
// Format thread
const threadView = views.thread(threadState, viewerDid)
Authentication
The AppView validates requests using JWT tokens:
import { AuthVerifier } from '@atproto/bsky'
const authVerifier = new AuthVerifier(
dataplane,
{
ownDid: config.serverDid,
alternateAudienceDids: config.alternateAudienceDids,
modServiceDid: config.modServiceDid,
adminPasses: config.adminPasswords,
}
)
// Verify request
const auth = await authVerifier.verifyAccessToken(req)
Feature Gates
A/B testing and feature flags via GrowthBook:
import { FeatureGatesClient } from '@atproto/bsky'
const featureGates = new FeatureGatesClient({
growthBookApiHost: config.growthBookApiHost,
growthBookClientKey: config.growthBookClientKey,
})
// Start client
featureGates.start()
// Check feature
const enabled = featureGates.isOn('new-feature', { did })
// Get variant
const variant = featureGates.getVariant('experiment-name', { did })
API Routes
The AppView implements all app.bsky.* Lexicons:
// Feed endpoints
app.bsky.feed.getTimeline
app.bsky.feed.getAuthorFeed
app.bsky.feed.getFeed
app.bsky.feed.getFeedGenerator
app.bsky.feed.getFeedGenerators
app.bsky.feed.getLikes
app.bsky.feed.getPostThread
app.bsky.feed.getPosts
app.bsky.feed.getRepostedBy
// Actor/Profile endpoints
app.bsky.actor.getProfile
app.bsky.actor.getProfiles
app.bsky.actor.searchActors
app.bsky.actor.getSuggestions
// Graph endpoints
app.bsky.graph.getFollowers
app.bsky.graph.getFollows
app.bsky.graph.getList
app.bsky.graph.getLists
app.bsky.graph.getListMutes
// Notification endpoints
app.bsky.notification.listNotifications
app.bsky.notification.getUnreadCount
Blob Serving
The AppView serves images and videos:
import { ImageUriBuilder, VideoUriBuilder } from '@atproto/bsky'
// Image URLs
const imgUriBuilder = new ImageUriBuilder('https://cdn.bsky.app')
const imageUrl = imgUriBuilder.getSignedPath({
did: 'did:plc:abc123',
cid: 'bafyreib2rxk3rh6kzwq',
format: 'jpeg',
fit: 'cover',
width: 1000,
height: 1000,
})
// Video URLs
const videoUriBuilder = new VideoUriBuilder({
playlistUrlPattern: 'https://video.bsky.app/%s/%s/playlist.m3u8',
thumbnailUrlPattern: 'https://video.bsky.app/%s/%s/thumbnail.jpg',
})
const playlistUrl = videoUriBuilder.getPlaylistUrl(did, cid)
const thumbnailUrl = videoUriBuilder.getThumbnailUrl(did, cid)
Database (Data Plane)
The data plane manages the indexed database:
import { Database } from '@atproto/bsky'
const db = Database.postgres({
url: process.env.DATABASE_URL,
schema: 'public',
poolSize: 20,
})
// Transactions
await db.transaction(async (dbTxn) => {
// Database operations
})
Resources