atproto
atproto is a wrapper library that supports the endpoints defined in Lexicon for com.atproto.*
.
All major endpoints of the AT Protocol API are supported, making it easy to manipulate data independent of specific services. If you want to use the Bluesky API, use bluesky instead.
If you want to use Bluesky APIs, see bluesky!
Features ⭐
- ✅ Zero Dependency
- ✅ Supports Powerful Built-In Retry using Exponential BackOff And Jitter
- ✅ Supports All Major Endpoints for
com.atproto.*
- ✅ Well Documented and Well Tested
- ✅ Supports Powerful Firehose API
- ✅ Supports OAuth DPoP
- ✅ 100% Null Safety
- ✅ Applicable to services other than Bluesky
See API Supported Matrix for a list of endpoints supported by atproto.
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
dart pub get
With Flutter:
flutter pub add atproto
flutter pub get
Import
Just by writing following one-line import, you can use all endpoints provided by atproto.
import 'package:atproto/atproto.dart';
Instantiate ATProto
You need to use ATProto object to access most of the features supported by atproto. And there are two ways to instantiate an ATProto object.
As shown in the following example, the key point in instantiating ATProto object is whether the endpoint you wish to use requires authentication.
See API Supported Matrix for whether or not authentication is required for each endpoint.
If authentication is required, first create a session with the ATP server using your credentials with the .createSession
function.
The credentials passed to the .createSession
function should be your handle or email address as identifier
and your password or app password as password
.
Your credentials will be sent safely and securely to the ATP server when you execute the .createSession
function. And it will return a Session
object with an access token once authentication is complete.
You then do not need to be particularly aware of the contents of the retrieved Session object, just pass it to the .fromSession
constructor of ATProto to safely and securely create an instance of the ATProto object.
import 'package:atproto/atproto.dart' as atp;
Future<void> main() async {
// Let's authenticate here.
final session = await atp.createSession(
identifier: 'YOUR_HANDLE_OR_EMAIL', // Like "shinyakato.dev"
password: 'YOUR_PASSWORD',
);
print(session);
// Just pass created session data.
final atproto = atp.ATProto.fromSession(
session.data,
);
}
Or, it's very easy if authentication is not required , simply use the .anonymous()
constructor.
import 'package:atproto/atproto.dart';
Future<void> main() async {
// Just call anonymous constructor.
final atproto = ATProto.anonymous();
}
See Session Management for more details about authentication.
Supported Services
atproto supports following services.
Once an instance of the ATProto object has been created, service endpoints can be used by accessing the property
corresponding to each service as follows.
import 'package:atproto/atproto.dart';
Future<void> main() async {
final atproto = ATProto.anonymous();
// Use `findDID` in `IdentitiesService`.
final did = await atproto.identity.resolveHandle(
handle: 'shinyakato.dev',
);
}
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(
collection: atp.NSID.create(
'feed.bsky.app',
'post',
),
record: {
'text': 'Hello, Bluesky!',
"createdAt": DateTime.now().toUtc().toIso8601String(),
},
);
// And delete it.
await atproto.repo.deleteRecord(
uri: strongRef.data.uri,
);
}
See API Support Matrix for all supported endpoints.
More Tips 🏄
Session Management
When using the AT Protocol API, there are endpoints that requires user authentication, and an access token created when a user is authenticated is represented as a Session
.
Okay, the most important factor here is how to create a session.
First, use the .createSession
function to create the most primitive session as follows.
import 'package:atproto/atproto.dart' as atp;
Future<void> main() async {
final session = await atp.createSession(
identifier: 'HANDLE_OR_EMAIL', // Like shinyakato.dev
password: 'PASSWORD', // App Password is recommended
);
print(session);
}
Then you can create ATProto object from authenticated session.
import 'package:atproto/atproto.dart' as atp;
Future<void> main() async {
final session = await atp.createSession(
identifier: 'HANDLE_OR_EMAIL', // Like shinyakato.dev
password: 'PASSWORD', // App Password is recommended
);
print(session);
// You can create ATProto object from authenticated session.
final atproto = atp.ATProto.fromSession(session.data);
// Do something with atproto
final did = await atproto.identity.resolveHandle(handle: session.data.handle);
}
App Password
App passwords have most of the same abilities as the user's account password, however they're restricted from destructive actions such as account deletion or account migration. They are also restricted from creating additional app passwords.
App passwords are of the form xxxx-xxxx-xxxx-xxxx
.
So, it's strongly recommended that App Password be used for login in AT Protocol's services.
Given the above reason, a possible use case is for the application to determine if the password given by the user is an App Password.
With atproto, you can easily determine if a password is in App Password format by using the .isValidAppPassword
function.
import 'package:atproto/atproto.dart' as atp;
Future<void> main() async {
atp.isValidAppPassword('xxxx-xxxx-xxxx-xxxx'); // => true
atp.isValidAppPassword('xxxxxxxxxxxxxxxxxxx'); // => false
}
Other Than bsky.social
The endpoints provided by atproto always access bsky.social
by default. But as you know, certain services such as Bluesky, built on the AT Protocol, are distributed services. In other words, there must be a way to access services other than bsky.social
as needed.
You can specify any service
as follows.
import 'package:atproto/atproto.dart' as atp;
Future<void> main() async {
final session = await atp.createSession(
// Add this.
service: 'boobee.blue',
identifier: 'YOUR_HANDLE_OR_EMAIL',
password: 'YOUR_PASSWORD',
);
final atproto = atp.ATProto.fromSession(
session.data,
// Add this, or resolve dynamically based on session.
service: 'boobee.blue',
);
}
De/Serialize
All objects representing JSON objects returned from the API provided by atproto are generated using freezed and json_serializable. So, it allows for easy JSON-based de/serialize of these model objects based on the common contract between the fromJson
and toJson
methods.
For example, if you have the following code:
import 'package:atproto/atproto.dart';
Future<void> main() async {
final atproto = ATProto.anonymous();
// Just find the DID of `shinyakato.dev`
final did = await atproto.identity.resolveHandle(
handle: 'shinyakato.dev',
);
}
Then you can deserialize DID
object as JSON with toJson
as follows:
print(did.toJson()); // => {did: did:plc:iijrtk7ocored6zuziwmqq3c}
And you can serialize JSON as DID
object with fromJson
as follows:
final json = did.toJson();
final serializedDID = DID.fromJson(json);
Thrown Exceptions
The following exceptions may be thrown as AT Protocol-related errors when using atproto. The specification of this exception conforms to the following document from the official.
Exception | Description | Retriable |
---|---|---|
XRPCException | Parent class of all the following exception classes. | ❌ |
UnauthorizedException | Thrown when a status code of 401 is returned from the ATP server. Indicating authentication failure. | ❌ |
RateLimitExceededException | Thrown when a status code of 429 is returned from the ATP server. Indicating rate limits exceeded. | ❌ |
XRPCNotSupportedException | Thrown when a status code of 1xx or 3xx is returned from the ATP server. Indicating unsupported error. | ❌ |
InvalidRequestException | Thrown when a status code of 4xx is returned from the ATP server. Indicating client error. | ❌ |
InternalServerErrorException | Thrown when a status code of 5xx is returned from the ATP server. Indicating server error. | ✅ |
Also, the following exceptions may be thrown due to temporary network failures.
Exception | Description | Retriable |
---|---|---|
SocketException | Thrown when a socket operation fails. | ✅ |
TimeoutException | Thrown when a scheduled timeout happens while waiting for an async result. |