Shared core layer for the AT Protocol (Bluesky) Dart ecosystem 🦋
Core Package for AT Protocol
atproto_core is the reusable foundation that the higher-level atproto and bluesky packages build on. It bundles the primitives every AT Protocol client needs — authenticated sessions, JWT decoding, a pluggable retry engine, HTTP/XRPC plumbing, blob and CAR handling — and re-exports the lower-level xrpc, multiformats, and cbor types so downstream packages depend on a single import.
You rarely construct atproto_core types directly in an app; you use it through atproto/bluesky. It is documented here because these are the building blocks those wrappers expose.
Install
With Dart:
dart pub add atproto_core
With Flutter:
flutter pub add atproto_core
Import
import 'package:atproto_core/atproto_core.dart';
Public API
Sessions
Session— a legacy (app-password) session:did,handle,accessJwt,refreshJwt, and optionalemail/didDoc/status.toString()redacts the JWTs so credentials never leak into logs. TheSessionExtensionaddsaccessTokenJwt/refreshTokenJwt(decodedJwt) andatprotoPdsEndpoint, which resolves the PDS host from the did document's#atproto_pdsservice, falling back to the access token'saud.OAuthSession,OAuthSessionManager,OAuthSessionRevokedException— re-exported fromatproto_oauthfor the OAuth authentication path. OAuth tokens are opaque and are never JWT-decoded.
JWT
decodeJwt(String)→Jwt— decodes a JWT's payload segment, throwing aFormatExceptionon malformed input.Jwt— the typed claim set (sub,aud,exp,iat,scope,clientId, …).JwtExtensionaddsisExpired,remainingTime, andatprotoPdsEndpoint.
Retry infrastructure
Retries are driven by a pluggable strategy so you can fully control backoff shape, which failures retry, and idempotency handling.
RetryStrategy— the interface:FutureOr<Duration?> nextDelay(RetryContext). Return a delay to retry, ornullto stop and let the error propagate.RetryConfig— the defaultRetryStrategy: capped exponential backoff (2 ^ (attempt - 1)) plusJitter, honoring a server-providedRetry-After/ratelimit-resetas a lower bound (capped at 60s). By default a procedure (POST) is not retried after an ambiguous failure the server may already have applied; setretryProcedureOnAmbiguousFailure: trueto restore unconditional retries. A negativemaxAttemptsthrowsArgumentError.RetryContext— the per-attempt input handed to a strategy:attempt,reason,isProcedure/isQuery,isAmbiguous,nsid,statusCode,error, and a resolvedretryAfter.RetryReason— the normalized failure category:timeout,serverError,rateLimited,network.Jitter— a[minInSeconds, maxInSeconds]random spread added to each backoff.RetryEvent— theonExecutecallback payload:retryCountandintervalInSeconds.
HTTP & XRPC clients
BaseHttpService— a thinget/postHTTP wrapper overxrpc, with injectable mock clients for testing.ServiceContext— the authenticated request context used by the wrappers: resolves the target PDS host, attaches Bearer/OAuth-DPoP auth headers, transparently refreshes expired sessions, and runs each request through the retryChallenge. Exposesget/post/stream.- Re-exported
xrpctypes:XRPCResponse,XRPCRequest,XRPCError,Subscription,RateLimit,HttpMethod,HttpStatus,GetClient,PostClient, and the exception hierarchy (XRPCException,UnauthorizedException,InvalidRequestException,RateLimitExceededException,InternalServerErrorException,XRPCNotSupportedException).Request,Response, andHttpExceptioncome fromxrpc/http.
Data types & converters
Blob/BlobRef— the atproto blob model ($type,mimeType,size,ref.$link), with aBlobConverterthat also normalizes the legacycidshape.AtUri,NSID— re-exported fromat_primitives, with matching JSON converters.CIDand the rest ofmultiformats, plus the simplecborcodec.
CAR decoding
decodeCar(Uint8List)→Map<String, Map<String, dynamic>>— decodes a Content Addressable aRchive (as returned by repo sync) into a map ofCID string -> block, normalizing CID links to{"$link": ...}and raw bytes to{"$bytes": ...}. Throws a typedCarExceptionon truncated or malformed input.
Utilities
isValidAppPassword(String)— checks thexxxx-xxxx-xxxx-xxxxapp-password format.
Usage
import 'dart:convert';
import 'package:atproto_core/atproto_core.dart';
void main() {
// A pluggable retry policy: exponential backoff + jitter, idempotency-safe.
final retryConfig = RetryConfig(
maxAttempts: 5,
jitter: Jitter(maxInSeconds: 4),
onExecute: (event) =>
print('retry #${event.retryCount} in ${event.intervalInSeconds}s'),
);
// Decode a JWT (e.g. Session.accessJwt) into typed claims.
final Jwt jwt = decodeJwt(rawAccessJwt);
print('expired: ${jwt.isExpired}, pds: ${jwt.atprotoPdsEndpoint}');
// Build an atproto blob reference.
const blob = Blob(
mimeType: 'image/png',
size: 12345,
ref: BlobRef(link: 'bafkreib...'),
);
print(jsonEncode(blob.toJson()));
}
See example/example.dart for a runnable version covering the retry engine, JWT decoding, blobs, and app-password validation.
For the full generated API reference, see the API documentation.