Working group: service describe
Objective: Define one XRPC query that any service in the ATmosphere can implement to say what it is: its roles, its capabilities, and the XRPC methods it supports. Use case: PDS implementations, feed services, permissioned data space hosts, AppViews, etc.
Expected deliverable: the community.lexicon.service.describe schema, validated example responses for the four service shapes above, and at least one reference implementation.
Timeline: iterate in this thread over the next few weeks, then a pull request to the lexicons repository once the design settles.
Looking for: people who run or build services (PDS, feed, AppView, relay, labeler, anything with an XRPC surface), and anyone with opinions about role and capability vocabularies.
Problem
AT Protocol applications and services have implied XRPC endpoints that are exposed, but as application updates occur and specs change, the version of those XRPC endpoints, their parameters, and their output can change without any notice.
Additionally, this becomes a real service-discovery problem when never-seen-before endpoints appear.
Design
The proposal is one unauthenticated query with no parameters that any service can implement. The community.lexicon.service.describe endpoint returns its roles, its capabilities, and its method surface. This grew out of conversations with Trezy and Nate, and it is better for them.
Solution
An XRPC query lexicon schema that returns an object with 2 fields. roles is an array of plain strings. methods is an array where each entry takes one of three shapes: an NSID, an AT-URI pointing at the lexicon schema record for a method, or a com.atproto.repo.strongRef that pins a schema record to an exact CID.
json
{
"lexicon": 1,
"id": "community.lexicon.service.describe",
"defs": {
"main": {
"type": "query",
"description": "Describes the roles, capabilities, and supported XRPC methods of a service endpoint. Allows any XRPC service to self-describe. Does not require authentication.",
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["roles", "methods"],
"properties": {
"roles": {
"type": "array",
"description": "The role or roles this service fulfills in the network (e.g., 'pds', 'appview', 'relay', 'labeler', 'feed-generator').",
"items": { "type": "string" }
},
"methods": {
"type": "array",
"description": "The XRPC methods this service supports, each referenced by NSID, by the AT-URI of its lexicon schema record, or by a strong (CID-pinned) reference to a lexicon schema record.",
"items": {
"type": "union",
"refs": ["#nsid", "#atUri", "com.atproto.repo.strongRef"]
}
}
}
}
}
},
"nsid": {
"type": "object",
"description": "A supported XRPC method, identified by its NSID.",
"required": ["value"],
"properties": {
"value": {
"type": "string",
"format": "nsid",
"description": "The NSID of the supported XRPC method."
}
}
},
"atUri": {
"type": "object",
"description": "A supported XRPC method, identified by the AT-URI of the lexicon schema record that defines it.",
"required": ["value"],
"properties": {
"value": {
"type": "string",
"format": "at-uri",
"description": "The AT-URI of the supported XRPC method's lexicon schema record."
}
}
}
}
}
The three reference forms answer different questions. An NSID is the compact form for well-known methods. An AT-URI says where the schema record lives, and because lexicon schema records use their NSID as the rkey, the NSID rides along inside the URI. A strongRef pins an exact revision: published lexicon records are mutable at their rkey, so the CID is the only way a service can say it implements this version, specifically. That last form matters most for methods that are not standardized anywhere yet, which is exactly the situation for newer service shapes.
Example request
There are no parameters and no authentication:
GET /xrpc/community.lexicon.service.describe
Host: pds.example.com
Or:
curl https://pds.example.com/xrpc/community.lexicon.service.describe
Example responses
A PDS
A PDS answers with its core surface plus, in this example, one nonstandard extension method. The extension is advertised with a strongRef because it is not standardized anywhere; the CID pins the exact schema revision the server implements.
{
"roles": ["pds"],
"methods": [
{ "$type": "community.lexicon.service.describe#nsid", "value": "community.lexicon.service.describe" },
{ "$type": "community.lexicon.service.describe#nsid", "value": "com.atproto.server.describeServer" },
{ "$type": "community.lexicon.service.describe#nsid", "value": "com.atproto.server.createAccount" },
{ "$type": "community.lexicon.service.describe#nsid", "value": "com.atproto.server.createSession" },
{ "$type": "community.lexicon.service.describe#nsid", "value": "com.atproto.identity.resolveHandle" },
{ "$type": "community.lexicon.service.describe#nsid", "value": "com.atproto.repo.createRecord" },
{ "$type": "community.lexicon.service.describe#nsid", "value": "com.atproto.repo.putRecord" },
{ "$type": "community.lexicon.service.describe#nsid", "value": "com.atproto.repo.deleteRecord" },
{ "$type": "community.lexicon.service.describe#nsid", "value": "com.atproto.repo.getRecord" },
{ "$type": "community.lexicon.service.describe#nsid", "value": "com.atproto.repo.listRecords" },
{ "$type": "community.lexicon.service.describe#nsid", "value": "com.atproto.repo.uploadBlob" },
{ "$type": "community.lexicon.service.describe#nsid", "value": "com.atproto.sync.getRepo" },
{ "$type": "community.lexicon.service.describe#nsid", "value": "com.atproto.sync.subscribeRepos" },
{
"$type": "com.atproto.repo.strongRef",
"uri": "at://did:web:pds.example.com/com.atproto.lexicon.schema/com.example.pds.getAccountUsage",
"cid": "bafyreidxl6igiec3hbdsrveibjq7wxzoupdvzzh2wgewibzfet5lii445q"
}
]
}
A feed service
A feed generator has a small, well-known surface, so plain NSIDs cover it. An empty capabilities array is valid; not every service has something to declare there.
json
{
"roles": ["feed-generator"],
"methods": [
{ "$type": "community.lexicon.service.describe#nsid", "value": "community.lexicon.service.describe" },
{ "$type": "community.lexicon.service.describe#nsid", "value": "app.bsky.feed.describeFeedGenerator" },
{ "$type": "community.lexicon.service.describe#nsid", "value": "app.bsky.feed.getFeedSkeleton" }
]
}
The Smoke Signal AppView
Smoke Signal indexes community.lexicon.calendar.* records and serves its own query methods on top of them. The method NSIDs here are illustrative, but the shape is what an events AppView returns:
json
{
"roles": ["appview"],
"methods": [
{ "$type": "community.lexicon.service.describe#nsid", "value": "community.lexicon.service.describe" },
{ "$type": "community.lexicon.service.describe#nsid", "value": "events.smokesignal.app.getEvent" },
{ "$type": "community.lexicon.service.describe#nsid", "value": "events.smokesignal.app.listRsvps" },
{ "$type": "community.lexicon.service.describe#nsid", "value": "events.smokesignal.app.getActorEvents" }
]
}