Service Capability Discovery

The S2S specification doesn’t appear to have an API for discovering what capabilities an Actor’s server supports. As an example, I happen to know that right now Mastodon doesn’t support AP features such as relationships, so I shouldn’t offer my users an option to use those features with actors running on Mastodon. If I did, the experience for my user would be confusing (“I sent Bob an Offer activity (friend request), but Bob never responded,” when Mastodon never presented Bob with the activity).

How can I as a developer (on behalf of my users), know what features are supported for a given Actor? Manually maintaining a table of (Server, Version) => [Feature] seems inadequate, but also seems like the only option available to me right now.

How is service interoperability supposed to work in this model?

As far as I know, you have two options:

  1. Specify the features your server supports in its NodeInfo
  2. Specify feature flags in each actor object, in your own namespace (see supportsFriendRequests in this actor as an example: https://friends.grishka.me/grishka).
2 Likes

I know it’s an old thread, but I recently searched for this and another mechanism @silverpill shared with me on fedi, which I think is also relevant to have in this thread (I’m using my own words here);

If a certain feature implies that a certain property is present on the object (and vice versa), then a check could be done for the presence of this property. This is only for these specific cases ofc. E.g. when a feature implies the presence of a collection (and vice versa), you could check if that collection is present.

1 Like

Since I just saw this thread while searching for a related topic, I’ll add a breadcrumb here for future readers:

The current (Jan 2026) way of advertising supported features appears to be FEP-844e: Capability discovery – this doesn’t directly answer the problem of knowing which base AP features are supported by which servers, but does solve the “knock knock” problem of figuring out if a server supports a recent FEP (or HTTP signatures): Fetch the server’s “server actor” record and cache the list of capabilities.

1 Like

i don’t think there’s a “current” way; what you’ve linked is simply the most recent proposal in a line of multiple proposals (and specifications).

your “entry point” for information in general is going to be one of the following:

  • /.well-known/host-meta defined in RFC 6415.
    • Note that the RFC requires web host metadata to be served in application/xrd+xml at least, although you may use the HTTP Accept header to content-negotiate for other formats, and requesting application/json or application/jrd+json is provided for in the RFC.
    • /.well-known/host-meta.json is also defined in the RFC as a way to skip content negotiation and always return the JRD.
  • /.well-known/nodeinfo defined in NodeInfo.
    • Note that NodeInfo is largely focused on statistics, and its schema makes certain statistics required instead of optional.
  • /.well-known/webfinger defined in RFC 7033, although you need to specify a resource:
    • ?resource=acct:domain.example@domain.example assumes that an acct URI (RFC 7565) whose userpart matches the host will always represent the host service.
      • Compare with email (SMTP as defined in RFC 5321), where a localpart of postmaster (case-insensitive) is special-cased and hard-coded to always go to the server itself.
      • Note that the result might be an individual (as:Person), in the case of personal websites as is popular in the IndieWeb ecosystem (where the distinction between the website and the person might not be made).
    • ?resource=https://domain.example assumes that the base domain always represents the service.
      • Note that it is unclear whether https://domain.example should be or is equivalent to https://domain.example/ in this processing context.
      • Note that it is unclear that the root resource / should always refer to the website or host itself and not to something else, such as its homepage.
  • HTTP GET defined in RFC 9110, although the resource name to request is arbitrary, and you may need to follow some link relation or property path.
    • GET /
      • Note that, as above with the WebFinger case where an https: URI is used, it is unclear that the root resource / should always refer to the website or host itself and not to something else, such as its homepage.
    • GET an actor, then check some link relation or property assumed to point to the host service:
      • https://www.w3.org/ns/activitystreams#generator, although this doesn’t always point to a “host service”, since the property is defined to point to a “generator” instead, like how an HTML document might use <meta name="generator" to specify some framework which generated the HTML document
      • You could define some “host” link relation or property whose definition is specified to point to “an entity hosting the current object”. The link target or property value would tell you where the host metadata is located.
      • https://www.w3.org/ns/ldp#inbox could be a better place to put such “capabilities”, since the idea of “capabilities” is more appropriately a property of the inbox and not a property of the service.

Personally, I dislike NodeInfo the most, as I do not think statistics should be required to be exposed, and I find it to be duplicative of the more general host-meta. However, I do recognize that the requirement to deal with XML is distasteful to a lot of developers, so full support for host-meta would not be popular. It is worth noting that host-meta actually used to widely supported in the OStatus days, as XML was already used for Atom feeds and even the precursor to WebFinger (LRDD, which behaved like WebFinger but also required XML while allowing JSON via content negotiation). These days, host-meta is still used around the web, but not so much in the fediverse. It might be feasible for the fediverse to have “partial support” by way of serving host-meta.json but not host-meta.

WebFinger is currently required to talk to Mastodon et al right now, but is also not strictly “necessary” if that’s not a consideration for you. Also note that Mastodon is exploring dropping the WebFinger requirement, so it might not be required forever.

I think the most straightforward approach is to use the actor’s identifier as a starting point, since you already have to fetch the actor for basically everything else. From there, I don’t think as:generator is the right path – while it is accurate to say that in many/most cases the actor’s “generator” is some host service, that doesn’t mean that “generator” and “host” are the same property or always necessarily equivalent properties. So I’d at least recommend defining a property for “host”:

@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
@prefix as: <https://www.w3.org/ns/activitystreams#>.

_:host
  rdfs:label "is hosted by";
  rdfs:comment "Indicates that the current resource is subject to some authority";
  rdfs:range _:Host.

# the following statements are debatable...
_:Host rdfs:subClassOf as:Service.

But I’d also recommend signaling the constraints on what kinds of messages an inbox can receive, and what it will do with them. In Linked Data Notifications, there is a concept of an inbox being “constrained by” (http://www.w3.org/ns/ldp#constrainedBy) something, perhaps a set of requirements or behaviors or some profile or specification or other. You could for example declare at minimum:

{
  "id": "https://actor.example/",
  "inbox": {
    "id": "https://inbox.example/",
    "http://www.w3.org/ns/ldp#constrainedBy": [
      {"id": "https://constraints.example/"}
    ]
  }
}

LDN recommends making these available in HTTP headers, so that you can do HEAD /inbox or similar, and obtain a Link whose rel is http://www.w3.org/ns/ldp#constrainedBy which might give more info.

There is one problem here that we are punting further down the road: what do any of these identifiers mean? How do we go from an identifier to knowing more about the constraints that are included within that thing? We have two options:

  1. Make definitions of constraints available out-of-band. For example, we might all agree that https://docs.joinmastodon.org/spec/activitypub or https://w3id.org/fep/xxxx should dereference to a certain set of constraints about what that inbox can or cannot receive. (To some extent, this is how context documents are handled in later JSON-LD adjacent specs, like VC/DID/CID – you are not supposed to dereference the @context identifiers at runtime.)
  2. Make definitions of constraints available in-band. For example, doing an HTTP GET against an http: or https: name should return some information about what those constraints are. But in most cases, this is better treated as a convenience for obtaining definitions and you should really be doing option 1 here.

We might then provide more and more detailed information about the constraints, using appropriate formats. Maybe a SHACL shape graph could describe exactly what shape activities are supposed to have. Maybe certain shapes would trigger certain behaviors, and a vocabulary for behaviors could be devises or agreed upon. You can take a natural language statement like:

This inbox supports payloads where
the `type` includes `Like`,
and `actor` and `object` are present with at least one value.

and turn it into something like this:

@prefix sh: <http://www.w3.org/ns/shacl#>.
@prefix as: <https://www.w3.org/ns/activitystreams#>.
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.

_:LikeShape
  sh:property
    _:LikesMustHaveTypeOfLike,
    _:LikesMustHaveRequiredProperties.

_:LikesMustHaveTypeOfLike
  sh:path rdf:type;
  sh:hasValue as:Like.

_:LikesMustHaveRequiredProperties
  sh:name "Required properties for Like activities";
  sh:description "Like activities must have at least one actor and one object";
  sh:path
    as:actor,
    as:object;
  sh:minCount 1.

or vice-versa, as it may be hardcoded into the processor’s logic. The granularity can be as simple as just an identifier, or it can be as detailed as providing the same information in both HTML natural language documentation as well as one or more RDF formats bearing a shape graph. The important thing is that everyone understands the same terms to mean the same things – for example, there should be no dispute about what https://www.w3.org/TR/activitypub includes in its constraints. If your inbox says it is constrained by ActivityPub and some other actor takes this to mean they can send you a Listen or Arrive activity, and you can’t actually handle Listen or Arrive activities, then this is a miscommunication due to ambiguity. For this reason, I think defining bespoke constraint profiles in something like a FEP is probably a better approach.

First, to restate the problem: As AP gains new features, some of them can’t be easily detected by probing for fields on an actor or post. (RFC 9421 HTTP signatures and migration collections are two recent examples.) So we’d like a way for a server to advertise a list of capabilities to other servers.

Long posts are my kryptonite, but if I understand correctly, you’ve listed several possible ways to do this:

  • /.well-known/host-meta.json: we could add capabilities as properties?
  • /.well-known/nodeinfo: unpopular option, and doesn’t fit with the “vibe” of nodeinfo as a sort of BBS directory that isn’t used for any AP protocol purpose
  • /.well-known/webfinger on base url: add extra fields listing capabilities?
  • actor record
    • a “host” field on an actor record could provide a bread crumb to the server actor
    • or, turn “inbox” field into an object which lists the inbox url and its capabilities

I don’t like the webfinger option because the server’s webfinger is part of discovering the server actor, so it’s roughly equivalent to putting new fields on the server actor record. Webfinger seems like it’s aimed at broader use than just AP, but the server actor is AP-specific, so between the two, the server actor feels more appropriate to me.

The inbox-as-object option is interesting, but I think it wouldn’t be backward compatible, because the current AP specs strongly imply that the inbox field is a single string url, and don’t describe any object format for it. So probably no existing server would be able to read it.

The server actor still feels like the best option to me, since there’s one server actor per server, and that’s the granularity of server capabilities.

“What are these capability properties?” is indeed the big unknown. I like using the FEP url or RFC url for a capability that represents support for a FEP or RFC, but I don’t have a strong opinion. It just didn’t seem objectionable when I read it in FEP-844e.

I don’t think this implication is there; AP/AS2 make no statements about schema, and any @type: @id property can be either a string, object, or array of strings or objects. This is a longstanding confusion and pain point of the document format when trying to have a specific processing model (which is out of scope / not fully constrained, but then again so was authentication and authorization…)

Whether this will break interop with existing softwares (and which ones) is a more interesting question that I don’t have an answer to. Expanding the actor’s inbox to be an embedded object is only a convenience for providing extra information about the inbox, just like you might have the outbox be an object so that you can save an HTTP request by communicating the outbox.totalItems directly in the actor. “How many posts have they made?” is a question answered in the HTML but not in the AS2. Ideally, multiple representations of the same thing should contain the same information.

If an embedded inbox object is problematic, then an HTTP OPTIONS request with Link headers is another option which is recommended by Linked Data Notifications (which defines the ldp:inbox that ActivityPub specializes):

OPTIONS /some-inbox HTTP/1.1
Host: domain.example

HTTP/1.1 200 OK
Allow: GET, POST, OPTIONS, HEAD
Accept-Post: application/ld+json, text/turtle
WWW-Authenticate: Bearer
WWW-Authenticate: example-auth-scheme-that-could-be-defined
Link: <https://constraints.example/>; rel="http://www.w3.org/ns/ldp#constrainedBy"
Link: <https://other-profile.example/>; rel="http://www.w3.org/ns/ldp#constrainedBy"

The important thing is that the constraints are a property of the inbox and not of the actor. The actor may change their inbox for one with different constraints.

The other important thing is, of course, what is meant by any given constraint identifier. If we all say our inboxes follow https://www.w3.org/TR/activitypub but we all disagree on what https://www.w3.org/TR/activitypub entails, then we haven’t really solved anything. Possible better candidates for this are FEPs containing a strict profile, or a project-specific documentation page or FEDERATION.md (ideally versioned, but that’s another problem…)

This isn’t true, either. The concept of “server actor” is not well defined, and there may be 0 or more such “server actors”. The capabilities are more appropriately tied to the inbox, which may be on a different server than the actor. (From the outside, it’s entirely opaque what is responding to the HTTP requests.)

Side note: it’s also possible for anything embedded in the actor document to drift out-of-sync with the current state of referenced resources, but this happens any time a link is used anywhere. Such is web!

Most of this is already covered in FEP-844e.

It defines two ways to advertise capabilities: through a server actor (which can be discovered using webfinger), and through the generator property of a non-server actor.

Advertising via inbox collection is an interesting idea, I might add that to the FEP. This method will be useful for nomadic actors where generator is not a server actor, because they are generated by a client.

the concept of “server actor” is still not well-defined – what makes an actor a “server actor”? a server can have zero or more actors. for example, pleroma/akkoma uses different “internal” actors for different purposes:

  • internal.fetch is used to proxy signed fetches. it is served at /internal/fetch.
  • relay is used to relay public posts from local users. it is served at /relay.

you could imagine many such actors which don’t correspond to users – perhaps a delivery actor delivers activities on behalf of local users, perhaps a moderation actor can be used to send and receive Flag activities or communicate with other mod teams, perhaps any number of other actors could be spun for specific use cases. whether any or all of these are considered “server actors” depends on the definition, and i’d be hard pressed to say that exactly one of these is the “server actor” without further clarification.

which URI should be used as the resource parameter on the webfinger query? i identified several possibilities:

  • acct:domain.example@domain.example
    • is userpart == host always the “server actor”?
  • https://domain.example/
    • is / always the “server actor”?
    • is https://domain.example equivalent?

previously, this ambiguity was solved by using /.well-known/host-meta to describe the host itself, without going through webfinger – in fact, you would previously go through there to find webfinger, before /.well-known/webfinger was defined. you could even use that well-known URI to represent the host itself, with a minimal extension to host-meta to allow content negotiation as application/activity+json (which is an option not precluded by RFC 6415) if you aren’t put off by having to serialize an XRD and JRD as well. otherwise, any arbitrary resource name could be used.

hopefully the two quotes above taken together should show the conflict. generator does not mean “server actor”. you shouldn’t assume “generator” and “server actor” are the same or equivalent property. they just sometimes have the same value.

i would say it’s more generally useful in all cases where you have an inbox that is an HTTP resource. using HTTP mechanisms for HTTP resources is entirely natural.

regarding FEP-844e specifically, i have commented on the actual discussion thread: FEP-844e: Capability discovery - #7 by trwnh

1 Like

FEP-844e refers to FEP-d556 (https://codeberg.org/fediverse/fep/src/commit/14311a2932a5119810395ebee7c3e73b67c91c5b/fep/844e/fep-844e.md#discovery-through-a-well-known-endpoint), which defines “server actor” as the result of a WebFinger query for a resource identified by base URL / server prefix.

  • / is not always a server actor.
  • FEP-d556 doesn’t seem to say that https://domain.example is equivalent to https://domain.example/. I think they are equivalent, but it would be better to discuss these details in FEP-d556 discussion thread (the proposal is FINAL but we can add some minor clarifications if necessary).