This is an informal guide to help new implementers get up to speed with the ActivityPub specification (published as a W3C Recommendation) and how to build projects with ActivityPub support.
Why might you choose to implement ActivityPub in your project? Possible reasons include:
- “to move beyond the silos of social media sites run by individual companies” (ActivityPub as it has been understood)
- Interoperability with a growing library of federated software
- To reuse and extend existing libraries.
- Collaboration with other projects looking to federate.
This guide includes links to many projects. The point of such links is not to recommend any specific project but to inform about the kinds of projects that would be useful to know about.
This guide is intended to expand on what’s in the relevant specifications. Those specs remain the primary sources and should be referred to during implementation.
If you’re used to specifications that contain a lot of jargon, you may be pleasantly surprised when you look at the ActivityPub specification. The authors put in a lot of effort to try to make the spec readable and easy to understand. A good starting place is the overview.
This guide is maintained by members of the SocialHub, a grassroots community of technologists and fedizens who together build the Fediverse ecosystem and evolve its standards. The community has a non-technical brainstorming area on Lemmy, called Fediverse Futures where you can gather feedback and elaborate ideas.
As an ActivityPub implementer you may find the following documents to be useful:
- Specification for ActivityPub
- Specification for ActivityStreams, the basis of ActivityPub.
- Specification for Activity Vocabulary, which details “extended types” for ActivityStreams actors and objects such as Person and Note.
- Specification for JSON-LD. ActivityPub and its two related specs, Activity Streams and Activity Vocabulary, all structure their data in JSON-LD. You may want to start with introductory material, such as:
- How to implement a basic ActivityPub server (Mastodon)
- Decentralizing Social Interactions with ActivityPub
- A Highly Opinionated Guide to Learning About ActivityPub
- Video (in Spanish), Mini implementación de ActivityPub en Ruby
- LibreLounge podcast
- ActivityPub as it has been understood–includes lots of relevant links.
- ActivityPub in Pleroma.
- PeerTube ActivityPub API.
- FunkWhale Federation
- What is ActivityPub? (MoodleNet)
- Mastodon ActivityPub API
As you start in, it’s useful to get a sense of what ActivityPub implementations and code projects already exist. Doing so will help you:
- Identify opportunities to collaborate and combine efforts.
- Identify examples and models to work from.
- See how your project relates to others.
- Connect with the ActivityPub community.
The delightful project maintains three up-to-date lists of Fediverse-related open source projects (these were formerly the ActivityPub Watchlists, migrated from Feneas when it shut down):
Additional places to look include:
- The Software category and Programming category on the ActivityPub SocialHub forum.
- A web spreadsheet of AP apps and their characteristics by @mayel from Social.coop.
- Fediverse.Party, which lists Fediverse projects including ones that support ActivityPub. You can find information in two places:
- The home page has links to a curated list of Fediverse projects. Click on each to find very detailed information on the projects, including the languages they’re written in.
- A list of more apps has more basic information on a bunch of other projects. On this page you can filter to see only apps that support ActivityPub.
- The various Fediverse aggregators, some of which include information on the software running each instance.
The ActivityPub, Activity Streams, and Activity Vocabulary specifications are the main places to look for details on what’s required to implement ActivityPub support. The notes here are intended to give pointers on areas not covered by the specs or where clarification or subsequent work is relevant.
Announce. An activity is an action undertaken by an actor.
Actor objects, like a
Group. Actors undertake activities like following or creating content.
Object (and link) types such as a
Video. These objects types usually represent content of some sort.
Collectionobjects. These are groupings or listings of multiple other objects, such as a list of followers of a particular person or a list of public posts.
Implementing ActivityPub support is about letting actors like site members on your application or site - in the Fediverse, often called an instance - have back and forth exchanges with actors on other instances. As discussed in Episode 12: ActivityPub Part 1 of the LibreLounge podcast (15:26 - 20:24), ActivityPub structures these exchanges in a way that’s analogous to a simple subject-verb-object statement. In every message that’s sent server-to-server:
Somebody is doing something to some object. In ActivityPub terms, that’s an Actor is performing an Activity on an Object.
In ActivityPub, two servers are federated with each other when there is a relationship between one or more actors on each. A common relationship is following: an actor on one server follows an actor on another server. From there, a whole set of other activities are opened up, like when the followed actor posts a
Video, the follower’s account receives a notification (in the form of a
Some data are tracked on both sides of the relationship. For example, if
@firstname.lastname@example.org, that relationship is tracked both as a record on example1.org (georgie is in chris’s following collection) and on example2.net (chris is in georgie’s followers collection).
ActivityPub URIs serve a dual role depending on the HTTP method used. Pretty much, a given URI such as the
id URI of a
Person or the URI of an actor’s
inboxsupports two types of operations:
- GET fetches an object or collection of objects.
- POST receives an activity in JSON-LD format.
Note that instead of the other standard HTTP methods - PATCH, PUT, DELETE - there are different activity types (
id of an object is a URI that can be “dereferenced”–that is, assuming appropriate access authorization, a GET request to the URI will look up and return the full object.
So URIs that are the
id of a single object such as an
Actor return data about that object. Other URIs, such as that of an actor’s
outbox, can return a
Collection of objects.
If you’re developing for ActivityPub, you’re going to be dealing with JSON-LD, since that’s the format ActivityPub JSON is in. While JSON-LD can be handled as regular JSON, there are also specialized tools in JSON-LD that may be valuable to your application.
One thing you’ll quickly notice when reading through code examples in the ActivityPub specifications is that the JSON-LD can be super variable in its format. Add to that the fact it will be coming from any number of different implementations and you’ve got a big challenge when it comes to parsing the incoming information.
So it’s worth noting that JSON-LD supports compacted and expanded formats as well as flattening of either. To avoid having to directly manage the inconsistencies of the compact format, you may want to standardize on expanded and flattened, then compact before returning. Flattening:
may drastically simplify the code required to process JSON-LD data in certain applications.
Discussion: More than JSON: ActivityPub and JSON-LD.
The JSON-LD specification is widely supported and you may find an implementation in your programming language that will provide basic functionality such as methods to expand, compact, and flatten. Packages include:
There’s an unofficial ActivityPubSchema that may be useful if you’re validating the JSON-LD of ActivityPub requests and responses.
While the ActivityPub specification covers a lot, there are also areas that aren’t spelled out and left to implementers to decide. As Serge Wroclawski put it in an episode of the Libre Lounge podcast (1:38 - 1:52):
If you’re an implementer and you first look at ActivityPub, it seems like there’s a lot missing, and there absolutely is. And so what you have to do is you have to look at best practices at the time, which aren’t necessarily in the standard itself.
This section is about those current practices.
How to look up actor accounts on another server?
In ActivityPub, an
Actor such as a
Person (often, a user with an account on a given site) is identified by an ID in the form of a URI. That URI could be what’s used to identify the actor on other ActivityPub sites. But URIs can be hard to work with and understand. A much more familiar format is
email@example.com (or, by convention in the Fediverse,
@firstname.lastname@example.org, to distinguish the handle from an email address).
The ActivityPub spec doesn’t cover profile discovery, that is, the ability to look up a handle like
@email@example.com and find the URI for the account’s profile. However, per ActivityPub? Well, it’s not that bad:
Most current implementers have integrated Webfinger for that purpose, since users like to work with handles rather than URLs.
Other resources as suggested inHow to retrieve “firstname.lastname@example.org” handle from actor’s URL:
- Investigate Nomadic Identity · Issue #216 · pixelfed/pixelfed · GitHub
- Webfinger account search issues · Issue #17133 · mastodon/mastodon · GitHub
- Ditching the webfinger requirement · Issue #17030 · mastodon/mastodon · GitHub
- Lemmy compatibility enhancements · Issue #17008 · mastodon/mastodon · GitHub
- [v4.0] Use local
idconsistently – not just API routes, but for URIs and internal references as well · Issue #10745 · mastodon/mastodon · GitHub
ActivityPub doesn’t itself specify how to handle server to server user authentication, but it provides some hints in a wiki page, SocialCG/ActivityPub/Authentication Authorization. In your implementation you’ll probably find yourself implementing at least one of the solutions they point to.
ActivityPub clients authenticate against a server using OAuth 2.0 bearer tokens.
So if you’re building a client app, or if you’re building a server app that supports client connections, you’ll need to build in OAuth 2.0 support.
Note: There are a lot of intricacies to C2S authentication. See this AndStatus issue Basic implementation of “client to server” ActivityPub protocol (#499) for a detailed discussion with many cross-references.
HTTP Signatures is the approach taken in most current implementations.
Usually you’ll need to both:
- Support HTTP signature verification from remote instances.
- Verify HTTP signatures on incoming activities.
The tutorial How to implement a basic ActivityPub server includes a good introduction to HTTP Signatures and how to use them, including how to add a
publicKey attribute to an actor object.
it is important to be able to verify that the actors specified in the
attributedTofields really did author the objects they are being claimed to have authored.
ActivityPub is all about actors, activities, and objects, but doesn’t say anything about the instance itself–the site that’s hosting all this. Various approaches are filling this gap.
There is a NodeInfo standard adopted by many ActivityPub implementations.
an effort to create a standardized way of exposing metadata about a server running one of the distributed social networks. The two key goals are being able to get better insights into the user base of distributed social networking and the ability to build tools that allow users to choose the best fitting software and server for their needs.
There is an alternate NodeInfo2 standard.
NodeInfo2 is a fork of NodeInfo which was seen as too complex for the problem it is solving.
There has also been discussion about the possibility of ActivityPub-specific discovery.
Complementary to NodeInfo, some implementations have introduced a federation.md file to describe instance capabilities in a human readable way. See this discussion for more information. The federation.md files are a good way to get a sense of how different projects are using and adapting ActivityPub. Examples include:
ActivityPub provides a way to connect to other instances. But what if there are instances a particular site’s administrators don’t want to connect to? How to deal with the fact that the Fediverse is by default unfiltered and there are nodes that permit or actively encourage hate speech and abuse?
Background on the problem and proposed solutions:
A very basic approach is to enable whitelisting/blacklisting of ActivityPub domains so that an individual site’s admins can decide who to or not to federate with. That’s what’s supported in Mastodon.
Relevant background from the Mastodon project:
- How the biggest decentralized social network is dealing with its Nazi problem
- Statement on Gab’s fork of Mastodon
- Documentation on server-wide moderation
- List of instances blocked by mastodon.social
- Mastodon instance with a forked codebase allowing whitelisting of domains rather than the default blacklist
While you may find the set of objects and properties spelled out in the Activity Streams specs is sufficient for your use case, in many cases it won’t be.
The upside of custom extensions is they meet a specific need. The downside is they limit interoperability with other ActivityPub implementations.
While you can definitely create your own extensions, it’s worth getting familiar with what other implementers have already done.
- Mastodon uses an extensive set of non-ActivityPub extensions.
Designed to address the most common questions, this section is a compendium of tips, suggestions, and ideas around specific implementation questions.
For starters, if you’re building a server app, you’ll probably want to display the ID of the user’s ActivityPub profile, since this is the way that others can interact with them in the Fediverse. If you’re using Webfinger, you may also want to display the
@email@example.com format so that users on other instances can use that to mention or look up your users.
Activity Vocabulary defines several types of objects, such as
Video. Currently there is some tension between interoperability, on the one hand, and adherence the spec, on the other. Some implementers are focusing on what’s currently supported in Mastodon, where a
Note will be fully displayed (minus certain markup) while an
Article, for example, will appear as a link.
ActivityPub content may be publicly addressed. It’s left to implementers to decide how to handle such content.
Questions to consider include:
- Should any locally generated public content be posted to all known
- If you’ve implemented a
sharedInbox, what if anything do you want to do with publicly addressed content that arrives there?
Among other uses, a shared inbox can optionally be used to send a single post to multiple actors who would otherwise need to each be messaged separately. When you are sending an activity to remote instances, instead of sending one per
Actor, you can instead iterate through all your actors and post to the list of unique
sharedInbox URIs. The ActivityPub spec notes:
in this scenario, the remote/receiving server participates in determining targeting and performing delivery to specific inboxes.
The idea here is that in theory a following relationship is tracked on both sides: as part of a
followers collection associated with the actor on the originating server and as part of a
followed collection associated with the actor on the receiving server. So the originating server doesn’t need to explicitly list recipients. Instead, the receiving server can take the ID of the activity’s
actor, look up its own list of who’s following that actor, and deliver the object to each member of that list.
Many web application developers will be used to a model where there’s a clear separation between (a) what’s created and managed within the application and (b) any data objects that are externally hosted. For example, in the most simple case, you might have your own
Article entities which you store locally. If you also, say, display remote content from an RSS feed, you might might model and store that content and its authors quite differently than your own
With ActivityPub, there’s at least the potential to use the same models for both local and remote objects. For example, if a user on your site both (a) posts
Article content of their own and (b) follows actors on remote instances and so gets content when those remote actors make
Article posts, you may decide to handle both local and remote articles in the same local model.
If you have an existing software and want to add ActivityPub support, it may help to look at what that’s looked like for projects that have already done it. Places to look include:
- The issue to add ActivityPub support to Mastodon.
Aided by a culture of collaboration among Fediverse advocates and participants, there is ongoing work to improve both the ActivityPub specification and interoperability among different ActivityPub implementations.
As you assess the work of implementing in ActivityPub it can help to be up on some of the areas of discussion within the community.
At https://blog.dereferenced.org and elsewhere, Ariadne Conill has critiqued the ActivityPub specification from a trust and safety perspective. See ActivityPub: The “Worse Is Better” Approach to Federated Social Networking and subsequent posts.
Others have critiqued the ActivityPub federation model’s reliance on external sites maintaining privacy restrictions. One commenter noted:
For the sake of privacy and safety I think that private posts should still only be pushed in stub/notification form (mostly because I don’t trust most ActivityPub implementations to keep private stuff private)
ActivityPub didn’t solve the longstanding federation issues which had been known about since before its creation, such as nomadic identity, key based identity rather than domain based identity and the ability to easily migrate accounts between instances.
While “ActivityPub” may mean something to people with a technical bent, it’s probably not the best way to convey to site members what your software is and does.
“Fediverse” is probably a bit better, in that at least if you look it up you’re slightly more likely to find something basically readable. Still, it’s worth looking around at how other software are handling the question of presenting the ideas of federation and ActivityPub to their users.
See the accompanying guides: