ActivityPub for developers

ActivityPub in theory and in practice

ActivityPub specification hasn't been updated since 2018. Many developers consider it incomplete and/or outdated.

  • Authentication and authorization are not specified. In practice, HTTP signatures are used for authentication during server to server interactions.
  • ActivityPub client to server API is underspecified. Vendor specific APIs are often used instead of it.
  • WebFinger is used to discover actors. Actual object identifiers are often hidden from the end user.

This guide is an attempt to document how ActivityPub actually works.

Objects

Serialization

The default data serialization format is JSON. JSON-LD processing is not required by the ActivityPub specification, and it is not recommended due to its negative impact on performance and increased implementation complexity.

However, all top-level objects should have a valid @context value that includes https://www.w3.org/ns/activitystreams, because some applications may not be able to process objects without it.

Tip

JSON-LD context can be verified in JSON-LD playground.

Identifiers

Every published ActivityPub object must have a globally unique identifier, unless it only exists as a part of another object (e.g. an attachment or a tag).

Identifiers are RFC-3986 absolute URIs. Don't use uppercase characters in case-insensitive URI components (scheme, host).

URIs may be normalized for the purpose of comparison as described in RFC-3986. However, when referring to a remote object (e.g. in to or inReplyTo field), the exact value of its id property must be used.

If an object can not be retrieved by its identifier, return 404 Not Found.

Properties

Use compact property names:

  • NO: https://www.w3.org/ns/activitystreams#inReplyTo
  • NO: as:inReplyTo
  • YES: inReplyTo

Ignore properties that are not supported.

Immutable properties

Values of these properties must not change during the object's lifetime:

  • id
  • type

Types

Use compact type names:

  • NO: https://www.w3.org/ns/activitystreams#Person
  • NO: as:Person
  • YES: Person

Use duck typing to determine the core type of an object. E.g. object with inbox and outbox properties is an actor. The recommended algorithm is specified in FEP-2277DRAFT.

Use duck typing when processing objects. For example, an object with attributedTo and content properties can be classified as "post".

Use nominative typing when working with activities. Ignore activities with unsupported types.

Prefer nominative typing when working with collections.

type property

Don't use multiple types. Type arrays are not widely supported.

Tip

In most situations, duck typing results in better interoperability compared to multi-typing.

Values

Properties that are not marked as "functional" in Activity Vocabulary may have the following data types:

  • A string.
  • An array of strings.
  • An object.
  • An array of objects.

When setting the value of a property to an object or an array of objects, prefer full representations.

Implicit ordering

Assume attachment, tag, to, cc and url values are ordered arrays.

Extensions

Use standard ActivityStreams types when compatibility is required.

If standard property is not appropriate for your use case, create a new one. New properties don't break compatibility.

Feature detection

Use properties to signal supported operations. Example: followers property in actor document indicates that actor supports the Follow activity.

In some cases detection based on the value of type is preferable. For example, the type of activity usually maps to a feature (with the exception of general purpose activities such as Create and Add).

Links

Links can only exist as parts of other objects. They should not have an id property.

Actors

Actor is an entity that performs activities.

Identifiers

Don't make username a part of actor identifiers and don't assume that WebFinger addresses are persistent. Usernames can be changed.

Types

The value of actor's type property is normally one of the actor types defined in Activity Vocabulary. However, consumers should not reject actors with other types.

Required properties

In addition to id, type, inbox and outbox properties, actor objects should have:

  • preferredUsername. Its value must match the "username" part of a WebFinger address. Therefore, it shouldn't contain characters that are not allowed in a WebFinger username. Actors on a server should have unique usernames.
  • publicKey. Represents an RSA public key used in HTTP signatures. Use FEP-521aFINAL to add more public keys.

The value of publicKey property is an object containing the following properties:

  • id: the identifier of the key (fragment identifier is recommended).
  • owner: the identifier of the actor.
  • publicKeyPem: the PEM-encoded value of the public key.

Outbox

Actor objects MUST have, in addition to the properties mandated by 3.1 Object Identifiers, the following properties:
outbox : An ActivityStreams OrderedCollection comprised of all the messages produced by the actor; see 5.1 Outbox.

Although outbox is required by ActivityPub specification, it is not necessary for federation. Return 404 Not Found or empty collection if outbox is not implemented.

Applications may use outbox to backfill profile data.

Updating

Actors are updated using the Update(Actor) activity.

Applications may also re-fetch a remote actor when they receive an activity.

Activities

Activities are used to synchronize data between servers.

All properties of activities are immutable. Activities can not be created, updated or deleted (via Create, Update or Delete activities). Activities should be idempotent.

Treat activities as notifications, not as commands.

Activities must specify their audience using to property.

Collections

Collections should be treated as dynamically generated views. Avoid adding properties to them those value may be changed directly (e.g. via Update(Collection) activity).

Don't use query parameters in collection identifiers. Query parameters are used to specify filters.

Authentication and authorization

At the very minimum, ActivityPub servers should implement HTTP signatures (Cavage draft). RFC-9421 is not widely supported.

All outgoing requests should be signed. POST requests (activity delivery) are usually signed by an actor representing a user. GET requests are usually signed by a server actor.

HTTP signature implementation details are documented in ActivityPub and HTTP Signatures report.

FEP-fe34DRAFT is recommended.

Audience

Any ActivityPub object can have audience, which is specified using to and cc properties. The audience is used for access control and to determine where activities should be delivered.

Activities and objects are assumed to be private if the audience is not specified. Actors and verification methods are assumed to be public. Collections are dynamically generated views and their audience is determined by the server.

Always use the fully-qualified identifier of the Public collection: https://www.w3.org/ns/activitystreams#Public.

Network

Push or pull?

ActivityPub's base mode of operation is "pull" (serving and retrieving objects). The "push" mode (delivery) is built on top of it. For example, there is no Create(Person) activity and actors must be retrieved from their origin.

Serving objects

Set the value of Content-Type header to application/ld+json; profile="https://www.w3.org/ns/activitystreams".

Retrieving objects

  • Set the value of Accept header to application/ld+json; profile="https://www.w3.org/ns/activitystreams" (required by ActivityPub standard).
  • Add User-Agent header.
  • Set request timeout.
  • Add HTTP signature to the request, even when retrieving a public object (some servers may block unsigned requests in order to make large-scale data collection more difficult).
  • Follow redirects, but set a limit. Request must be re-signed after every redirect.
  • Set limit on response size.
  • Implement protections against Server Side Request Forgery. At the very least, requests to localhost, private and unspecified IPv4 and IPv6 addresses must be blocked. Note that IPv6 addresses can be mapped to IPv4 addresses.

The value of Content-Type header in response must be either application/ld+json; profile="https://www.w3.org/ns/activitystreams" or application/activity+json (to prevent attacks like the one described in GHSA-jhrq-qvrm-qr36).

Delivering activities

  • Set the value of Content-Type header to application/ld+json; profile="https://www.w3.org/ns/activitystreams".
  • Add User-Agent header.
  • Add HTTP signature to request.
  • Do not follow redirects.

Retry delivery if the server is not available or when response status code is one of the following:

  • 401
  • 429
  • 5xx

Exponential backoff is recommended. Stop delivering if the server is unreachable for a prolonged time.

When a server responds with 410 Gone, treat it as equivalent to receiving a Delete(Actor) activity. This rule does not extend to 404 Not Found status, which might be temporary.

Receiving activities

Return 202 Accepted status code if activity is processed asynchronously.

Return 410 Gone if the user account doesn't exist or has been permanently deleted.

HTML content

Sanitize HTML content.

Values of summary and content properties are HTML by default.

In addition to sanitizing HTML content, applications may strip HTML tags that are not important to them. The set of allowed tags often depends on the object type (e.g. embedded media may be allowed in a content of an Article, but not in a content of a Note).

Tip

Information on allowed HTML tags can be found in the Support Tables.

Contentful objects

Contentul object is something that can be displayed as a "post".

Such objects should have attributedTo and content properties.

Protocol features

Following

An actor can follow another actor by sending a Follow activity.

The recipient may respond with an Accept or Reject activity. If the relationship has already been established, and a new Follow activity is received, the server should always respond with an Accept activity.

If an actor doesn't automatically send an Accept activity upon receiving a Follow, it should have manuallyApprovesFollowers property with value true.

A previously followed actor can be unfollowed with an Undo activity.

A follower can be removed with a Reject activity.

Posts

Posts are represented as Note and Article objects.

The compatibility table for different object types can be found at funfedi.dev site.

A post can be published with a Create activity. It can be later updated with an Update activity, or deleted with a Delete activity.

Translations

The contentMap property is used to specify variants of content in different languages.

Conversations

Replies

A reply should have an inReplyTo property indicating the post being replied to.

An application can retrieve the entire conversation by following inReplyTo references.

Followers-only

Followers-only posts are posts addressed to actor's followers collection.

Some applications create replies to followers-only posts addressed to the replier's followers collection. This leads to fragmented conversations where each branch has different audience.

To produce conversations with a consistent audience, applications should address replies to the followers collection of the top-level post author.

Reposts (boosts)

Reposts are represented as Announce activities.

A repost can be deleted with an Undo activity.

Groups

Groups are described in FEP-1b12: Group federationFINAL. Real implementations may deviate from that specification, but better specification doesn't exist at the moment.

Custom emojis

Custom emojis are documented in FEP-9098: Custom emojisDRAFT.

Reactions

Likes, favorites and upvotes are represented as Like activities. Dislikes and downvotes are represented as Dislike activities.

A like or dislike can be deleted with an Undo activity.

Emoji reactions are documented in FEP-c0e0: Emoji reactionsDRAFT.

Polls

Polls are documented in FEP-9967: PollsDRAFT.

Quote posts

There are two different ways to implement a "quote post":

Software compatibility table can be found at funfedi.dev site.

Migrations

Migration of followers is described in FEP-7628: Move actorDRAFT.

Relays

Relay protocols are documented in FEP-ae0c: Fediverse Relay Protocols: Mastodon and LitePubFINAL.

WebFinger

WebFinger protocol is used to translate an user@host address to the corresponding actor ID. This mechanism is documented in ActivityPub and WebFinger report.

Split-domain setup

Split-domain setup is used when the "host" part of a WebFinger address needs to be different from the host name of actor's ActivityPub server. More information can be found on WebFinger Split-Domain Canary website.

NodeInfo

NodeInfo best practices are documented in FEP-0151: NodeInfo in Fediverse Software (2025 edition)FINAL.

Miscellaneous

FEP-67ff: FEDERATION.mdFINAL is recommended.

Useful tools and resources

Related work

References