| title | Custom XRPC Calls |
|---|---|
| description | Call private services, new lexicons, and raw AT Protocol endpoints before generated descriptors exist. |
Generated descriptors are the best path for stable known lexicons. Raw XRPC calls are still useful when you are integrating with a private service, testing a new lexicon, or writing a small exploratory tool.
Use PoptartClient.get and PoptartClient.post from poptart when you want
the app-facing client. Use poptart_xrpc directly only when you are building a
lower-level transport wrapper.
dart pub add poptartimport 'package:poptart/poptart.dart';Use anonymous clients for public endpoints.
Future<Map<String, dynamic>> resolveHandle(String handle) async {
final client = PoptartClient.anonymous();
final response = await client.get<Map<String, dynamic>>(
NSID.parse('com.atproto.identity.resolveHandle'),
parameters: {'handle': handle},
);
return response.data;
}Raw calls return the type you ask for. For exploratory endpoints,
Map<String, dynamic> is usually the most flexible output type.
Use PoptartClient.fromSession or PoptartClient.fromOAuthSession when an
endpoint needs auth.
Future<Map<String, dynamic>> getRepoStatus(Session session, String repo) async {
final client = PoptartClient.fromSession(session);
final response = await client.get<Map<String, dynamic>>(
NSID.parse('com.atproto.sync.getRepoStatus'),
parameters: {'did': repo},
);
return response.data;
}The client adds the right authorization headers for the session type.
Use post for XRPC procedures. The body can be a map or a generated model's
toJson() result.
Future<Map<String, dynamic>> callCustomProcedure(
Session session,
String subject,
) async {
final client = PoptartClient.fromSession(session);
final response = await client.post<Map<String, dynamic>>(
NSID.parse('com.example.moderation.markSubject'),
body: {
'subject': subject,
'reason': 'reviewed',
},
);
return response.data;
}For stable AT Protocol repo writes, prefer generated descriptors and generated
input models. Raw post is best when the method is not generated yet.
Poptart defaults to the public Bluesky AT Protocol services. Override the service when you are targeting a self-hosted PDS, a staging service, or a private XRPC server.
final client = PoptartClient.anonymous(
service: 'https://pds.example.com',
);
final response = await client.get<Map<String, dynamic>>(
NSID.parse('com.example.status.get'),
);You can also override the service for a single call:
final response = await client.get<Map<String, dynamic>>(
NSID.parse('com.example.status.get'),
service: 'https://staging.example.com',
);Start raw while the lexicon is still moving:
final response = await client.get<Map<String, dynamic>>(
NSID.parse('com.example.feed.getTimeline'),
parameters: {'limit': 30},
);After the lexicon is generated, switch to the generated method or descriptor:
import 'package:example_poptart/com/example/feed/get_timeline.dart'
as get_timeline;
final response = await client.call(
get_timeline.comExampleFeedGetTimeline,
parameters: const get_timeline.FeedGetTimelineInput(limit: 30),
);client.call(get_timeline.methodDescriptor, ...) is the same typed request with
the descriptor spelled explicitly. Both forms give consumers typed inputs,
typed responses, and generated JSON conversion.
Use poptart_xrpc directly when you do not want the higher-level Poptart
client at all.
dart pub add poptart_xrpcimport 'dart:convert';
import 'package:poptart_xrpc/poptart_xrpc.dart' as xrpc;
Future<void> main() async {
final response = await xrpc.procedure<String>(
xrpc.NSID.create('server.atproto.com', 'createSession'),
body: {
'identifier': 'alice.example.com',
'password': 'app-password',
},
);
final session = jsonDecode(response.data) as Map<String, dynamic>;
print(session['did']);
}Most apps should not need this layer. It is for transport-focused packages, small experiments, and cases where you intentionally do not want generated clients.
Avoid raw calls for stable, generated lexicons. Generated descriptors catch misspelled fields, produce typed output, and keep code easier to refactor when schemas change.
Avoid raw response maps deep in your app. Convert them near the boundary or promote the lexicon to generated code once the endpoint is part of your normal workflow.