atproto

atproto is a comprehensive Dart/Flutter library that wraps all AT Protocol API endpoints defined in com.atproto.* Lexicons.
This package focuses on core AT Protocol functionality, making it ideal for building applications that work with any AT Protocol service, not just Bluesky. All major endpoints are supported, enabling service-agnostic data manipulation and infrastructure development.
Use atproto for:
- Multi-service AT Protocol applications
- Infrastructure tools and bridges
- Core protocol functionality only (
com.atproto.*endpoints) - Minimal dependency footprint
Use bluesky for:
- Bluesky-specific applications
- Social features (feeds, follows, chat)
app.bsky.*andchat.bsky.*endpoints- All-in-one development experience
π‘ Note: The bluesky package includes all atproto functionalityβyou don't need both.
Features ββ
- β
Small Dependency Footprint - Only
atproto_core,xrpc, and the two codegen annotation packages - β
Pluggable Retry - An opt-in Exponential BackOff And Jitter policy that is idempotency-safe, plus a
RetryStrategyinterface to replace it wholesale - β
Comprehensive API Coverage - Supports All Major Endpoints for
com.atproto.* - β Multiple Authentication Methods - Session-based auth with transparent refresh, OAuth 2.0 with DPoP, and anonymous access
- β Real-time Firehose API - Stream live events from AT Protocol services, already decoded and typed
- β Production Ready - Well documented, thoroughly tested, and actively maintained
- β Type Safe - 100% null safety with comprehensive error handling
- β Service Agnostic - Works with any AT Protocol service, not just Bluesky
- β Rate Limit Handling - Built-in rate limit detection and management
- β Union Type Support - Handles complex AT Protocol data structures safely
- β Injectable Transport - Supply your own HTTP client for tests, proxies, or tracing
See API Supported Matrix for a list of endpoints supported by atproto.
If you need social features like feeds, posts, follows, or chat functionality, consider using bluesky instead:
- Social Graph: Follow/unfollow users, manage lists, handle blocks and mutes
- Feed Operations: Create posts, like/repost content, manage timelines
- Chat Features: Send messages, manage conversations, handle reactions
- Notifications: Real-time updates and notification preferences
- Video Support: Upload and manage video content
- Rich Content: Handle images, external links, and rich text formatting
The bluesky package includes all atproto functionality plus these Bluesky-specific features.
Getting Started πͺβ
Installβ
See the Install Package section for more details on how to install a package in your Dart and Flutter app.
With Dart:
dart pub add atproto
With Flutter:
flutter pub add atproto
Both commands will automatically run pub get to fetch the package and its dependencies.
Importβ
Import the main atproto library to access all AT Protocol endpoints:
import 'package:atproto/atproto.dart';
For OAuth authentication, also import:
import 'package:atproto/atproto_oauth.dart';
For advanced Firehose functionality:
import 'package:atproto/firehose.dart';
Create ATProto Instanceβ
The ATProto class is your main entry point for accessing AT Protocol services. There are three ways to create an instance depending on your authentication needs:
See API Supported Matrix for whether authentication is required for each endpoint.
1. Session-based Authentication (Recommended)β
For most applications, use session-based authentication with your handle/email and password:
import 'package:atproto/atproto.dart' as atp;
Future<void> main() async {
// Create session with your credentials.
final session = await atp.createSession(
identifier: 'your.handle.com', // Your handle or email
password: 'your-app-password', // App password recommended
);
// Create ATProto instance from session. An expired access token is
// refreshed transparently using the session's refresh token.
final atproto = atp.ATProto.fromSession(session.data);
// Now you can use authenticated endpoints.
final profile = await atproto.repo.getRecord(
repo: session.data.did,
collection: 'app.bsky.actor.profile',
rkey: 'self',
);
}
An instance built this way keeps its own session alive: when a request comes back 401 because the access token has expired, the refresh token is spent, the session is replaced, and the request is retried once. See Authentication.
2. OAuth Authenticationβ
For applications requiring OAuth 2.0 with DPoP, use atproto_oauth. Every stage of the flow is pluggable, and authorize persists the per-authorization state in an OAuthStateStore (in-memory by default), so callback takes only the redirect URL β you no longer thread a context object through by hand.
The primary constructor is ATProto.fromOAuth, which takes an OAuthSessionManager. The manager owns DPoP header building and transparent token refresh, so it is what you want whenever you hold on to a client for more than one request. ATProto.fromOAuthSession is a convenience wrapper that builds the manager for you from a session.
import 'package:atproto/atproto.dart' as atp;
import 'package:atproto/atproto_oauth.dart';
Future<void> main() async {
// Use your client metadata.
final metadata = await getClientMetadata(
'https://atprotodart.com/oauth/bluesky/atprotodart/client-metadata.json',
);
final client = OAuthClient(metadata);
// Resolve the account and get the authorization URL. Returns a plain Uri;
// the per-authorization state is stored in the client's OAuthStateStore.
final authUrl = await client.authorize('shinyakato.dev');
print(authUrl);
// Make user visit authUrl
// final callback = await FlutterWebAuth2.authenticate(
// url: authUrl.toString(),
// callbackUrlScheme: 'https',
// );
// Complete the exchange with only the callback URL. The `state` parameter in
// it is used to recover the stored context.
final session = await client.callback(
'https://atprotodart.com/oauth/bluesky/auth.html'
'?iss=xxxx&state=xxxxxxx&code=xxxxxxx',
);
print(session.accessToken);
print(session.pds);
// Later, restore the stored session (refreshing it if it has expired), or
// refresh/revoke it explicitly.
final restored = await client.restore(session.sub);
// final refreshed = await client.refresh(session);
// await client.revoke(session);
// The primary constructor takes an OAuthSessionManager, which owns DPoP
// header building and transparent token refresh.
final atproto = atp.ATProto.fromOAuth(
OAuthSessionManager.fromSession(restored!, client: client),
);
// `fromOAuthSession` is a thin wrapper over the above.
final same = atp.ATProto.fromOAuthSession(restored, oauthClient: client);
}
fromOAuthSession without an oauthClient uses the session as-is and cannot refresh it. Once the access token expires, every request fails with 401.
See the atproto_oauth guide for the full pluggable flow, OAuthSessionManager, and session persistence.
3. Anonymous Accessβ
For public endpoints that don't require authentication:
import 'package:atproto/atproto.dart';
Future<void> main() async {
// Create anonymous instance.
final atproto = ATProto.anonymous();
// Use public endpoints.
final did = await atproto.identity.resolveHandle(handle: 'bsky.app');
print(did.data.did);
}
See Authentication for more details about authentication.
Supported Servicesβ
atproto provides access to all core AT Protocol services through dedicated service classes:
| Property | Class | Lexicon | Description |
|---|---|---|---|
| admin | AdminService | com.atproto.admin.* | Server administration, account and subject status |
| server | ServerService | com.atproto.server.* | Account management, sessions, app passwords |
| identity | IdentityService | com.atproto.identity.* | Handle resolution, DID operations |
| repo | RepoService | com.atproto.repo.* | Record CRUD operations, blob uploads |
| moderation | ModerationService | com.atproto.moderation.* | Content reporting and moderation |
| sync | SyncServiceImpl | com.atproto.sync.* | Repository synchronization, Firehose API |
| label | LabelService | com.atproto.label.* | Content labeling and queries |
| lexicon | LexiconService | com.atproto.lexicon.* | Lexicon schema resolution |
| temp | TempService | com.atproto.temp.* | Temporary/experimental endpoints |
sync is typed as SyncServiceImpl rather than the generated SyncService. It adds subscribeReposAsMessages, which yields decoded, typed Firehose messages, on top of every generated com.atproto.sync.* method.
Service Usage Examplesβ
Once you have an ATProto instance, access endpoints through their corresponding service properties:
import 'package:atproto/atproto.dart' as atp;
Future<void> main() async {
final atproto = atp.ATProto.anonymous();
// Identity Service - Resolve handles to DIDs.
final didResult = await atproto.identity.resolveHandle(handle: 'bsky.app');
print('DID: ${didResult.data.did}');
// Server Service - Get server information.
final serverInfo = await atproto.server.describeServer();
print('Server: ${serverInfo.data.availableUserDomains}');
// Label Service - Query content labels.
final labels = await atproto.label.queryLabels(
uriPatterns: ['at://did:plc:example'],
);
print('Labels found: ${labels.data.labels.length}');
}
For authenticated operations:
import 'package:atproto/atproto.dart' as atp;
Future<void> main() async {
final session = await atp.createSession(
identifier: 'your.handle.com',
password: 'your-app-password',
);
final atproto = atp.ATProto.fromSession(session.data);
// Repo Service - Create a record.
final record = await atproto.repo.createRecord(
repo: session.data.did,
collection: 'app.bsky.feed.post',
record: {
'text': 'Hello from AT Protocol!',
'createdAt': DateTime.now().toUtc().toIso8601String(),
},
);
// Repo Service - List your records.
final records = await atproto.repo.listRecords(
repo: session.data.did,
collection: 'app.bsky.feed.post',
);
print('Created record: ${record.data.uri}');
print('Total posts: ${records.data.records.length}');
}
See API Supported Matrix for a list of endpoints supported by atproto.
Let's Implementβ
Okay then, let's try some endpoints!
The following example first authenticates the user against bsky.social, sends the post to Bluesky, and then deletes it using a reference to the created record.
import 'package:atproto/atproto.dart' as atp;
Future<void> main() async {
final session = await atp.createSession(
identifier: 'YOUR_HANDLE_OR_EMAIL', // Like "shinyakato.dev"
password: 'YOUR_PASSWORD',
);
final atproto = atp.ATProto.fromSession(session.data);
// Create a record to specific service like Bluesky.
final strongRef = await atproto.repo.createRecord(
repo: session.data.did,
collection: 'app.bsky.feed.post',
record: {
'text': 'Hello, Bluesky!',
'createdAt': DateTime.now().toUtc().toIso8601String(),
},
);
// And delete it. `uri` is already an `AtUri`, so it can be destructured
// directly.
final uri = strongRef.data.uri;
await atproto.repo.deleteRecord(
repo: uri.hostname,
collection: uri.collection.toString(),
rkey: uri.rkey,
);
}
See API Support Matrix for all supported endpoints.
More Tips πβ
These topics behave identically whether you drive them through atproto or
bluesky, so they are documented once in the Guides
section rather than twice here:
| Guide | What it covers |
|---|---|
| Authentication | Creating, persisting and refreshing sessions; app passwords; two-factor sign-in |
| Custom Services | Pointing the client at your own PDS, a relay, or the public AppView |
| Error Handling | The exception hierarchy, and what rate limiting actually does |
| Retries and Timeouts | Retry strategies, backoff, and the idempotency guard that protects procedures |
| Pagination | Cursor loops, and the condition that actually ends one |
| Working with Data | Serialization, sealed union types, and unknown fields |
| Lexicon IDs | The generated constants for every Lexicon and object ID |
| Firehose | Streaming repository events, and staying connected |
| HTTP Client | Injecting a shared, proxied, instrumented or fake transport |
Related Packagesβ
Need Bluesky Features?β
If you're building a Bluesky application, consider upgrading to bluesky:
- Includes all atproto functionality
- Adds social features (feeds, posts, likes, follows)
- Chat functionality with conversation management
- Video upload and management
- Rich notification system
Text Processingβ
For advanced text processing in Bluesky posts:
- bluesky_text - Rich text analysis and facet generation
Package Overviewβ
See the Package Overview for a complete list of all available packages and their relationships.