From 4b0b97bf96ed7a2e6da6d4f9e4149cba2c84d383 Mon Sep 17 00:00:00 2001
From: silverpill A Fediverse Enhancement Proposal (FEP) is a document that provides information to the Fediverse community. The goal of a FEP is to improve interoperability and well-being of diverse services, applications and communities that form the Fediverse. The FEP Process is an initiative of the SocialHub developer community, a liaison of the W3C Social Web Incubator Community Group. For ongoing and past discussion see the SocialHub FEP category. Do you have an idea, opinion or information that you want to share with the wider Fediverse community? You may do so with a Fediverse Enhancement Proposal (FEP). To create and submit a FEP: Use the identifer as the \"slug\" when filling out the frontmatter. Write down your idea in the newly created file and commit it to a new branch in your repository (ex. fep-xxxx). Alternatively to steps 3. to 6., you can run that should create a prefilled template for you. The list of FEP's is maintained by the facilitators who are listed in the FACILITATORS.md file. Facilitators are neutral custodians of the FEP process, who merge PR's and create tracking issues. Do you have ideas to improve the FEP Process? Post your suggestions to the issue tracker, or on the SocialHub forum. The SocialHub developer community is a \"DoOcracy\" which means: \u201cpick up any task you want, and then steer it to completion\u201d. Your contributions are most welcome, so delve in and find out how you can help. CC0 1.0 Universal (CC0 1.0) Public Domain Dedication To the extent possible under law, the authors of this document have waived all copyright and related or neighboring rights to this work. NodeInfo is a protocol intended to standardize upon a way to provide server-level metadata to the public. This enables tools and clients to utilize this metadata to assess server health or facilitate end-users choices about servers and software to use on the Fediverse. This document is a revised version of FEP-f1d5: NodeInfo in Fediverse Software, which was published in 2020. The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119. Servers SHOULD implement NodeInfo protocol version 2.1. Some information contained in the NodeInfo document might be considered sensitive by server operators. The NodeInfo endpoint could be disabled to hide that information. All numeric properties in the Servers MUST NOT publish incorrect usage statistics. Servers SHOULD NOT rely on NodeInfo data for protocol negotiation and capability detection because server operators might intentionally disable NodeInfo. Servers MUST NOT infer capabilities from the Protocol-specific capability discovery mechanisms could be used instead. The capabilities of an ActivityPub application can be advertised using a server actor, as described in FEP-844e: Capability discovery. Properties of the Example: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work. Some properties represent special collections, such as: Verifying that any given object is part of a special collection is usually only possible by resolving that collection and checking its items one-by-one until the current object is found. This can be inefficient to verify. It would be easier if there was an inverse claim for each claim made about an object being part of a special collection. This FEP aims to define some properties that can be used to make those inverse claims. Given the special collections above, we can map the following claims and inverse claims. The last two inverse claims are particularly salient, since inclusion in Claim : Activity This is not particularly useful to prove. Claim : Activity This is not particularly useful to prove, and at best can only be implied if Claim : Actor Equivalent claim : Actor Another equivalent claim : Actor Inverse claim : Actor Equivalent inverse claim : Actor Another equivalent inverse claim : Actor This can be verified by showing one of the following: Claim : Actor Equivalent claim : Actor Another equivalent claim : Actor Inverse claim : Actor Equivalent inverse claim : Actor Another equivalent inverse claim : Actor This can be verified by showing one of the following: Claim : Actor This can be verified by showing a trusted activity exists in Note that there is an issue that may occur if Claim : Object If The Like activity Claim : Object The Announce activity Claim : Object Inverse claim : Object The inverse claim can be verified by showing that an activity exists where: - Claim : Object Inverse claim : Context collection The inverse claim can be verified by showing that an activity exists where: - We can map proofs onto the Provides proof that some object was added to some On an activity where On a On a non-activity Object where Example of an activity with Example of a Provides proof that some object was added to some On an activity where the On a On a non-activity Object where Example of an activity with Example of a Provides proof that the current activity was added to the On a Example: Provides proof that the current activity was added to the On an Example: Warning Experimental, requires further thought. Warning Currently bugged. See https://github.com/w3c/activitystreams/issues/593 for more details. Provides proof that the current relationship is reciprocally claimed. Relationships other than following or being a follower may be proved using this property, but the requirements for such a proof are out of scope of this FEP. We define the extension property Example that proves a user is following another user: Example that proves a user is followed by another user: Miscellaneous examples: Verifying stamps has an issue with bootstrapping trust. In addition to verifying stamps via logical inference, ActivityPub clients SHOULD also consider the following: Upon receiving an activity with a side effect of adding something to a special collection, ActivityPub servers SHOULD generate and deliver an The recipient of a stamp SHOULD Revoking a stamp can be done by: - If it is resolvable, making it no longer resolve - If it is signed, then waiting for the signature to expire - This flow is similar for any stamp that uses Actor 2 likes a post by actor 1: Actor 1 sends a like stamp: Actor 2 updates their Like activity with proof: If actor 1 had provided cryptographic proof, actor 2 would be able to authoritatively serve the stamp until the proof expired. Actor 1 can silently revoke the stamp by making it no longer resolve, or actively revoke the stamp by issuing a revocation activity [TODO: Update with \"undoneBy\" pointing to a Remove?] This flow uses Some actor creates some object that is part of some context, owned by some context moderator: The context moderator adds the object to the context: The actor who created the object now can issue two updates: one for the Create activity to add a CC0 1.0 Universal (CC0 1.0) Public Domain Dedication To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work. Provides proof that some object was added to some Domain : Object with context set to a Collection that has attributedTo set Range : Add Provides proof that some object was added to the replies collection of some inReplyTo object Domain : Object with inReplyTo set to an Object that has a replies collection Range : Add Provides proof that the current relationship is reciprocally claimed. Domain : Relationship Range : Relationship | Add | Follow? | Accept Follow? Status : Experimental Example 1: Example 2: Most current Fediverse applications use an acct-URI as unique display name for actors. Usually, this display is done by displaying In addition to the example below, we wish to point out that further independence of webfinger will enable new features such as using domain names as handles. Consider building an application that posts new entries of an RSS feed to the Fediverse. Let's assume we are interested in the updates of the bovine python library available through RSS at https://pypi.org/rss/project/bovine/releases.xml. Then an uri for this actor might look like This would enable There is now no obvious choice for CC0 1.0 Universal (CC0 1.0) Public Domain Dedication To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work. This document proposes a representation of quote posts that allows verifying consent of the quoted user, through a revocable authorization mechanism, as well as a representation of the user's choice regarding whetheir their posts can be quoted and by whom. The approval mechanism defined in this document is systematic and required for all quotes except self-quotes, but as with The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119]. In the remaining of this document, \u201cquoted object\u201d refers to the object being quoted, \u201coriginal author\u201d to its author, and \u201cquote post\u201d refers to the object quoting the \u201cquoted object\u201d. (This section is non-normative.) This proposal has benefitted from significant discussions on SocialHub as well as discussions with trwnh and GoToSocial developers. In fact, the A \u201cquote post\u201d is represented as an object with a This example is non-normative, and the (This section is non-normative.) While this FEP introduces We believe each of those to have significant drawbacks, such as re-using a namespace that has no definition for them, implying the value is an URL or URI, or using an unusual naming scheme, and none of them are linked to a control mechanism like the one defined in this FEP, hence why we introduced That being said, we suggest some of them as fallback for compatibility with existing fediverse software implementations. Users may not want their posts to be quoted, or not by everyone. To allow users to express that, we re-use GoToSocial's interaction policies and define a Each quotable object advertises an A sub-policy is defined by two attributes: - Interactions from actors that are neither in To advertise a policy of disallowing all quotes, Note that the policy is entirely advisory. It SHOULD be used to provide user interface hints such as enabling a \u201cQuote\u201d button or explaining why an object cannot be quoted, but it MUST NOT be used to verify whether a quote post is valid. See later sections for the actual verification mechanism. In order to enforce a policy, we rely on approval stamps, a mechanism used to tell third-party servers that a quote is approved, regardless of the current state of the policy. Quote approval stamps are objects of the type The A When a third-party attempts to dereference the The following stamp can be used to prove that actor To be considered valid for a particular quote post, a An approval stamp can be revoked by The The When receiving a The receiving end MAY inspect the If the quote post is considered acceptable, the original author MUST reply with an If the quote post is considered unacceptable, the authority SHOULD reply with a In order to get approval, the quote post author MUST send a The quote post SHOULD be inlined in the The quote post author MAY wait until they receive an If the author of the quote post receives a If the implementation waits for the Otherwise, it MAY translate as a If the author of the quote receives an Depending on whether they already sent a When processing a quote post from a remote actor, a recipient MUST consider them unapproved unless any of those conditions apply: - the author of the quote post and that of the original post are the same (same The original author may want to perform /a posteriori/ moderation of the quote posts, or block a quote poster in particular. To do this, the original actor MUST The original author MUST NOT embed the Upon receiving a Additionally, if the recipient owns the quote post, it MUST forward the Because getting revocation properly forwarded depends on the good will of the revoked post's author, it may be necessary to have other means of checking whether an approval has been revoked. For this reason, recipients SHOULD re-check the This proposal has been made with great care to not require new server behavior, allowing ActivityPub clients to implement this proposal without requiring generic ActivityPub server software to implement additional logic. In particular, this is the reason the approval stamp is a separate object rather than the An alternative approach we considered is using a dedicated endpoint to check for approval of a quote. This would effectively allow externalizing approval verification to a separate mechanism, but while this would not require new server behavior, this would still require a new server component to be specified, which is why we opted for the mechanism described in this specification instead. (This section is non-normative.) To clients that do not implement this FEP, quote posts are seen as regular posts with no explicit relation with the quoted post. While this is preferable to having the quoted post relayed without the comment, this is still lacking significant semantic context. Therefore, quote posts should be authored in such a way that their contents include a reference to the quoted post, e.g. by adding Using a special class like (This section is non-normative.) Servers that do not implementing the current FEP will still be able to quote the post without providing any dogpiling-reducing friction. There is unfortunately nothing we can do about that. However, servers which do implement the current FEP should refuse displaying those quotes, so implementing the control mechanisms of the current FEP remains worthwhile. The current FEP does not specify behavior around the visibility of quoted posts, but implementations should pay special attention to not inadvertently expose the contents of a quoted post to users who are not allowed to see it. In particular, implementations should ensure that any restriction based on user blocks, or follow relationships are still respected. Likewise, implementations should pay attention so that any discoverability, searchability or privacy setting, such as FEP-5feb, are still respected. To reduce risk of accidental exposure through third-party servers with insufficient visibility checks, it is recommended to not allow quoting posts with a wider allowed audience than that of the quoted post. Effectively revoking authorized quote posts relies on the participation of the quote poster's server to effectively reach the audience of the quote post. This means that an ill-intentioned server which obtained an authorization could deliberately refuse to forward the revocation. Still, the ability to revoke a quote post remains useful between well-intentioned servers, and opportunistic re-verification of quote approvals should also help with discovering that a quote authorization has been revoked, despite the potential lack of forwarding. By not adding a hash or copy of the reply in the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work. This FEP introduces a server-wide endpoint for delivering activities to multiple inboxes. sharedInbox currently allows for doing this, but it requires the remote server to know how to deliver the activity based on its addressing properties. However, the remote server might not know how to deliver the activity to private recipients, or recipients within a collection. The multibox endpoint removes this knowledge requirement from the receiving server and instead makes the sending server responsible for marking inboxes to explicitly deliver to. (This section is non-normative.) sharedInbox allows servers to reduce network traffic when delivering Public activities to multiple recipients, but it doesn't work when delivering to collections of actors, or when delivering to private audiences using bto or bcc. If an activity is addressed to a collection and that activity is delivered to a remote sharedInbox endpoint, then this introduces a requirement for the remote server to know the contents of the collection (or at least the local subset of its contents). In the common case of delivering activities addressing followers collections to a sharedInbox endpoint, then the remote server must first recognize that this collection id is specifically a followers collection, and then guess which local actors are following that activity's actor. This is prone to issues and can also have disastrous outcomes when follower state drifts out of sync. We can eliminate the dependency on shared follower state and allow for delivery of non-Public activities by introducing a new endpoint that likewise allows delivering to multiple inboxes, but without requiring the remote server to know anything. This enables addressing arbitrary collections as well as the use of (This section is non-normative.) There are various advantages described in the original multibox proposal: Shared Inbox provides the ability for server to server communication traffic to be reduced from R requests, where R is the number of recipients, to a single HTTP request. This is a desirable property as it reduces the amount of HTTP round trips for both the sender and receiver. Unfortunately the design of Shared Inboxes as described in the ActivityPub specification makes it very easy for a spammer to abuse the system by not requiring explicit delivery recipients. We propose an alternative to Shared Inbox called MultiBox that keeps the desirable properties of Shared Inbox while protecting against scenarios in which the sender uses Shared Inbox to \"spam\" a server. Like Shared Inbox, MultiBox consists of a single HTTP endpoint for multiple Actors. Unlike Shared Inbox, in a MultiBox request, each recipient is explicitly listed by Inbox, requiring both the knowledge of the Actor and a corresponding Inbox for that actor. This information is transmitted through the use of an HTTP header Audience where each Inbox is listed using comma separated values. This has two advantages over Shared Inbox. Used on its own, it eliminates the vulnerability mentioned previously whereby recipients to a message do not need to be listed. If this proposal is adopted alongside the Object-Capabilities Based Inbox proposal (4.5), the advantages multiply as we also gain the ability to appropriately filter incoming messages according to the criteria set out by the specific Inboxes, as well as letting us know the origin of each Inbox. For the sender, the additional computing resources required to send a MultiBox request are minimal, but doing so would make mass-messages expensive for senders wishing to abuse the system. One open question on this proposal is that if we use the HTTP header Audience to store the list of recipients, this may result in a limitation. HTTP header sizes are not explicitly capped at the protocol level but implementations often cap them at different lengths- 4Kb for the Nginx web server or 8Kb for Apache. This would limit the number of per message recipients, though this limitation would rarely be reached. An alternative to this proposal would be a new MultiBox object encapsulating the Audience field and the ~Activity. (This section is non-normative.) This FEP uses the \"alternative\" approach of putting inboxes in the body of the POST request, instead of using an HTTP header. Whereas headers are limited to possibly as low as 4Kb by default, the limit on POST request bodies is usually much higher; Nginx by default uses a 1MB limit for POST requests. This is the difference between 4000 characters versus 1 million characters. Actors MAY have a A server MAY reduce the number of delivery requests by identifying all recipients which share the same Upon receipt of such an activity, the receiving server MUST add the activity in (This section is non-normative.) Discovering the multibox endpoint: Alternatively, without a context declaration: Delivering to the multibox endpoint: For transient activities or for cases not requiring a fetch (e.g. no access control), you can embed the activity inline: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work. URI : Label : multibox endpoint Comment : An optional endpoint used for wide delivery of activities to multiple inboxes. POST an Add activity where the object is at least one activity to be delivered, and the target is at least one inbox to deliver to. Domain : A map of Range : The endpoint (@id) Required : No Functional : Yes Is defined by : FEP-0499 Discovering a multibox endpoint with additional context Discovering a multibox endpoint without additional context Delivering to a multibox endpoint This specification addresses sometimes difficult interactions with ActivityPub resources hosted on remote servers. It defines a custom URL scheme which can be used by custom web-based protocol handlers to route hyperlinks to those resources to the user's preferred server. It additionally advises when ActivityPub servers can include these links in HTML views they generate, and how clients and servers can implement those web-based protocol handlers. When a person follows a link to some ActivityPub powered site, the browser will navigate to the resource on that site. This is often not what the person would have prefered when they have an account on a different ActivityPub aware site. In order to interact with the linked resource from their own account, the person must separately open their home server and search for the resource. This is an awkward and sometimes confusing process, especially for people who are not already familiar with the idosyncracies of navigating a federated social network. Web-based protocol handling is a feature of modern web browsers. This allows a site to register with the user's browser as a handler for custom URI schemes. The browser will send links using that scheme to the registerd handler. This way, it's possible to direct links to the user's preferred server or application. The handler can retrieve the resource and provide familiar and appropriate presentation. Native ActivityPub clients can also take advantage of these URIs to provide similar handling. Similar proposals such as Fedilinks and Mastodon Issue 19679 have been made, and were even briefly implemented in Mastodon. There seems to be some concensus that custom protocol handlers have the technical capability to solve the problem of difficult interactions with cross-instance objects. There is also ample prior art for this approach. It seems the main impediment to early attempts was a perception of poor UX and limited adoption. This seems to be mostly a chicken-and-egg problem. That will always be a problem, until it's not. In this case, it seems it would be helpful to let standards drive implementation, as the opposite hasn't ocurred. This proposal also recommends behavior that is compatible with gradual adoption. Some similar proposals have also disussed emphasizing outcome, rather than content. Encapsulating action is an understandable desire, and this proposal attempts to facilitate that. But the protocol aspect is firmly focussed on interaction with ActivityPub objects, without making any assumptions about the design or capabilities of current or future ActivityPub services. The hope is that this gives the resulting implementations better longevity. Documents can outlive software, and people should still be able to interact with them in useful ways. This also keeps the proposal scoped just to ActivityPub concerns, without imposing on other standards. The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this specification are to be interpreted as described in RFC-2119. This proposal refers to ActivityPub objects as viewed and represented in multiple ways from multiple servers. For clarity, these are the terms that will be used to describe those scenarios. Origin server means the server that has authority for the ActivityPub object. This is the server that hosts and controls access to the object, and will recieve requests for the URI used as the object's ID. Handler is any software that handles the ActivityPub object means the JSON-LD document representing an ActivityPub Object, as described in the ActivityPub and ActivityStreams[ActivityStreamsRef] specifications. HTML representation means an HTML or other document rendered to display the ActivityPub object for human use. A client is any software that provides a human-friendly presentation of ActivityPub objects, or can interact with an ActivityPub server. For example, this could be a server's web UI or a native mobile app. This software does not necessarily utilize the C2S profile of the ActivityPub spec. When creating hyperlinks to ActivityPub resources, individuals and applications SHOULD include a link using the custom The address provided using the The linked address MUST replace the scheme used with A Including an encapsulated intent is OPTIONAL. Responding to the intent is also OPTIONAL. The creator of the intent should keep in mind that the handler MAY support intents, but does not have to. Also keep in mind that the handler MUST provide a mechanism for the user to confirm or decline to perform the intended action. The use or purpose of the The format of the query parameter to encapsulate an intent is The For security and privacy, Below are some examples of One-click check in A link to enable one-click check in to pick up a shopping order. One-click follow A link to enable one-click following from another website. The handler must not perform the follow activity unless the user confirms it. Link with no intent A link to view a blog post published as an ActivityPub Article ActivityPub applications that can retrieve remote objects and generate an HTML display MAY also implement a web-based protocol handler for the When resolving the given URI, the handler MUST ignore any provided username or password component, and it MUST exclude those components from the requests it makes to retrieve the object. If the given URI includes a query string with an intent parameter, the handler MUST exclude the intent parameter from the requests made to retrieve the object. The handler MUST NOT attempt to retrieve objects from a relative URI. There's likely no good reason for a The handler MUST perform the same sanitization or other safe handling of untrusted URIs as it normally would. For example, there's likely no good reason for a production system to try to load resources from localhost, or using an ip address, rather than a hostname. Doing so could also facilitate phishing or scams. When retreiving resources identified by a Other than the above considerations, the handler MUST attempt to resolve the URI exactly as given. After successfully retrieving the referenced object, the handler SHOULD produce an HTML representation of the object and present it for the user. The handler MUST escape and sanitize the content of the object before displaying it, to prevent cross-site scripting attacks. The handler SHOULD display the object in the normal way for the application, and in the normal context. For example, if the application normally displays replies alongside an object, it SHOULD do so in this case as well. The handler SHOULD enable the user to interact with the object normally, such as to generate Like or Announce activities referring to it. When the provided The handler MUST NOT perform any of these activities in response to an ecapsulated intent: ActivityPub servers MAY offer users an option to register with their browser as a Clients MAY offer users an option to register as handlers for If a client will retrieve the linked object itself, the client MUST adhere to the same specifications from section 3.1 and 3.2 as any other application would. Because there is no way to know or control which application will ultimately handle any CC0 1.0 Universal (CC0 1.0) Public Domain Dedication To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work. This document describes a minimal implementation of a federated marketplace based on ActivityPub protocol and Valueflows vocabulary. In such marketplace actors can publish offers and requests, respond to offers and requests published by other actors, enter into agreements and exchange information necessary to complete these agreements. Extension of ActivityPub protocol with Valueflows vocabulary was initially proposed by Lynn Foster in FEP-d767. The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119. Valueflows defines proposals as published requests or offers, sometimes with what is expected in return. The representation of a proposal is a JSON document with the following properties: Intents are proposed economic transactions. The primary intent describes what is being offered or requested, and reciprocal intent describes what is expected or offered in return. Some examples: The representation of an intent is a JSON document with the following properties: Minimal example: Full example: Proposals can be linked to actors (if actor provides a service) or to other objects (if they represent economic resources) using FEP-0ea0 payment links. Proposals can also be added to public collections, or be delivered to actor's followers using If FEP-0ea0 payment link is used, its Example of a proposal attached to an actor via payment link: Consuming implementations which don't have marketplace features MAY display proposals similarly to An interested party responds to a proposal and then parties start negotiating to reach an agreement. To respond to a proposal, an interested party MUST send an In the first case, the proposer finalizes the agreement and sends In the second case, the proposer sends The representation of an agreement is a JSON document with the following properties: Commitments are promised economic transactions. The representation of a commitment is a JSON document with the following properties: The first commitment MUST satisfy the primary intent of the proposal. The second commitment MUST satisfy the reciprocal intent of the proposal (if present). The units specified in the agreement MUST match the units specified in the proposal. If the value of Example of an The The quantities specified in the finalized agreement MUST match the quantities specified in The finalized agreement MAY have Example: The Activity MAY contain Example: Economic transaction happens outside the protocol. When both parties complete their parts of the transaction, the proposing party MUST publish a confirmation. The type and structure of confirmation activity may vary between different marketplaces, but it MUST contain a reference to the Example: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work. This FEP describes a way to attach payment information to ActivityPub actors and objects. That information might be a link to donation page, a link for buying an artwork, or anything else that can be represented with a URI. PeerTube videos may have FEP-8c3f: Web Monetization was published in 2022. The ensuing discussion on SocialHub forum led to the creation of this proposal. The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119. Payment link is an object with the following properties: Payment links MUST be added to Payment link attached to an actor: Payment link attached to an object: (This section is non-normative.) Implementers may treat payment links attached to actor object in the same way as actor metadata fields. In that case, CC0 1.0 Universal (CC0 1.0) Public Domain Dedication To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work. ActivityPub's federated model allows for flexibility in referencing data between different instances. However it requires that these instances be always online and do not allow for non-internet or locally published identities outside of the HTTPS/DNS based web. This document describes how implementors can extend ActivityPub to link to objects hosted on Peer to Peer protocols and how compatible clients should detect this support and load each others' content. ActivityPub's federated model allows for flexibility in referencing data between different instances. However it requires that these instances be always online and do not allow for non-internet or locally published identities outside of the HTTPS/DNS based web. By linking to P2P versions of documents from HTTPS versions, we can enable interoperability with existing AP implementations while opening the way to P2P-only networks. There are existing specifications in place for linking to \"alternate\" URLs for content. These are already used by implementations to resolve ActivityPub identities from web pages, to link to web pages from ActivityPub objects like an Actor or a Note, or to discover the ActivityPub account of a user referenced with Our approach builds on these methods by adding extra URLs pointing to P2P versions of resources along side the expected HTTP links. Compliant clients should attempt to prioritize loading P2P versions of AP data when possible / enabled by users. One of the easiest ways is to sit beside the content negotiation support with links to HTML pages. Statically published AP sites which cannot do content negotiation via Accept headers have the option to point to the ActivityStreams version of a site using a When loading a link to an HTML page, clients should attempt to detect the Similar to the HTML links, published sites should add the P2P URLs of their AP Actors after the HTTP version within the When attempting to resolve a user from a webfinger style mention like FEP-fffd set the foundation for linking to alternate versions of content. P2P clients should expand on this by adding links to p2p versions of all AP objecs served via HTTP. Note that the P2P objects should have their When loading a JSON-LD file associated with an Activitypub object (Actor, Note, Collection, etc), clients should check the For accounts that don't want to set up their own HTTP server, an HTTP gateway may be used. In this case, have your P2P site loadable on a gateway on its own subdomain. For example on Actor HTTP version Actor IPNS version: Webfinger doc CC0 1.0 Universal (CC0 1.0) Public Domain Dedication To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work. FEP 7888 lays out some guidelines for use of the This proposal aims to extend these guidelines further by codifying: The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in [RFC-2119]. A context MUST have an owner. The following are upgrades to FEP 7888 that pertain to ownership: A resolvable context collection (see FEP f228) declares an owner by setting Inheritance is considered only when a new object being published has a relation (typically via There are situations where a relation exists but context is explicitly not inherited (e.g. forked topic, quote post). These situations are out of scope of this FEP. The object MUST inherit Implementors SHOULD map that inherited context to a local identifier (if applicable) to support future use-cases/activities. When publishing an object with a Note If an object is at the root/top-level, then there is nothing to inherit and a new context should be generated. When consuming an object declaring a Note The remote CC0 1.0 Universal (CC0 1.0) Public Domain Dedication To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work. Media Attachments are ubiquitous in the Fediverse. My quick investigation into the explore tab on My goal in this FEP is to document current usage, and issue recommendations on how to improve it. These recommendations are based on the support table Recommended Media Attachment Format available at FunFedi.dev. For developers that enjoy making their keyboards smoke, I believe that the above link combined with the content of Testing should be enough to adapt their Fediverse applications. The other parts are meant for people, who which to improve the situation related to media attachments. We will discuss our basic suggested format with the following example of an image attachment. There is a lot to say here, first how does this relate to communication in ActivityPub which is done by activity. For this consider the activity (taken from data.funfedi.dev) given by We note that the media attachment is contained in the array of In particular, one should emphasize that a media attachment not having an To use media attachments, an object MUST have an This can be expressed as the json-schema: We now discuss the specific form a media attachment. In addition to Furthermore, media attachments SHOULD contain a Again this can be represented as a json-schema. Many current implementations use Let's write a bit of python to determine if something is a media attachment based on This already is fairly complicated, but there is more. If one allows There are more exceptions and poor configuration to treat, e.g. url could be an empty list, or the implementation could attach The goal of a specification needs to be to simplify the code that needs to be written, so we insist on people using the types In this section, we discuss properties related to the linked file. The linked file is retrieved either by performing a Let's look back at our original example of a media attachment Here the linked file is given by a The properties Finally There is something missing in the file properties: access control, see the section Authentication and Authorization in the open questions below. The creator of a media attachment SHOULD include the values of We have now discussed all properties of our example document except for where at least In order to standardize these things further work is needed. As it is currently not supported in the Fediverse, I will just give the basic example how to use multiple attached Links: As the example shows, this is useful to attach both a low quality version (54kb) and a high quality one (2.2MB) of a video. We think that supporting this will open the door for richer applications. By using json-schema, one can validate some level of correctness of generated media attachments. Relevant schemas are available at Fediverse schemas for media attachments. They can be combined into a feature test using Gherkin, see Media Format. If you wish to validate everything, including digest, you can use the examples provided at FunFedi.dev. This section is essentially a todo list for the community on stuff that should be fixed, but isn't yet. The picture in the examples was created based on this picture available for free on pixabay by photographer derekmuller. Unfortunately, the current standards to not let me attach this information to my media object. One could now say that this could be solved with just using the See FEP-c118 and its discussions for more on the topic. Currently, image links must be accessible without any form of authorization. This is due while communication between a user and their server requiring authentication and between servers requiring authentication, images are often stored on third party services, e.g. S3, thus adding authentication is hard. For some approaches to resolve this see this Fediverse discussion. One approach to achieve authentication and authorization easily with existing technologies would be Bearcaps. For a different approach see also Binary Fediverse transport. Storing media is costly. It is thus important to avoid duplication. By having a digest for all media through the Consider posting a song, e.g. something from the brat summer, then you might want to attach the album cover, e.g. an image featuring the color One might want to extend the schema for media attachments to convey this information. A failure of ActivityPub is that it restricts transport to be JSON. ActivityPub thus forces people to use external means, e.g. download the file, to convey media content. One could solve some problems, e.g. Authentication and Authorization, by just allowing transport of binary blobs. This would require an extension of the wire format. Allowing for messages to contain binary blobs would also for sharing media via thick clients. Apparently Streams has some mechanism for protecting attachments. Media URLs in non-public posts look like this: https://{domain}/photo/{filename}.jpg?token={token} IIRR at least in Hubzilla that token is just part of OpenWebAuth's \"magic authentication\". Where I guess the token contains info about which instance(s?) to contact in order to verify your identity. The audience is kept in the media server(s) database and sync'ed between clones. CC0 1.0 Universal (CC0 1.0) Public Domain Dedication To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work. hide: - navigation description: \"(This section is non-normative) Prior FEPs (FEP-7628, FEP-E965) describe an ability for an Actor to move to a new id, often hosted on a different server instance, however they do not describe a mechanism for moving objects that are owned4 by that actor. This FEP describes a mechanism of migrating objects owned by a moved Actor to the target instance using two OrderedCollections created by the target instance a migration collection that contains a mapping from source object URIs to new URIs on the target instance, and a moves collection that contains the actor Move activities that prove a migration has taken place and allows verification of object signatures in the case the source Actor is no longer available. This FEP attempts to balance effectiveness, performance, security, and ease of implementation by allowing 3rd-party instances to gradually update their local copies of the affected Objects. This FEP describes a \"Push\"-style migration6 initiated by a source instance followed by a \"pull\" of objects by a target instance, as well as a \"Pull\"-style migration initiated by a target instance given a prior export of actor data. The migration operation is agnostic to the type of the Objects being migrated, supporting protocol evolution to unanticipated Object types across instances with varying support for them. Collection-based object migration is orthogonal to, and compatible with content-addressed or other portable object schemes (e.g. FEP-ef61). tl;dr to migrate objects, create a mapping from the old to new objects on the target instance, and let 3rd-party instances gradually migrate their local representations using that map.\" (This section is non-normative) Prior FEPs (FEP-7628, FEP-E965) describe an ability for an Actor to move to a new This FEP describes a mechanism of migrating objects owned by a moved Actor to the target instance using two This FEP attempts to balance effectiveness, performance, security, and ease of implementation by allowing 3rd-party instances to gradually update their local copies of the affected Objects. This FEP describes a \"Push\"-style migration6 initiated by a source instance followed by a \"pull\" of objects by a target instance, as well as a \"Pull\"-style migration initiated by a target instance given a prior export of actor data. The migration operation is agnostic to the type of the tl;dr: to migrate objects, create a mapping from the old to new objects on the target instance, and let 3rd-party instances gradually migrate their local representations using that map. (This section is non-normative) Following an Actor migration, the new, \"target\" Actor is considered equivalent to and the successor of the prior \"source\" Actor. Despite being declared as equivalent2 Moving objects is hard: - The combinatorics of every third-party instance needing to update every object after a move make object migration potentially forbiddingly costly. This is particularly true when considering ActivityPub in \"pub/sub-only\" mode where actions must be processed immediately upon receipt: a target server emitting 100,000 However ActivityPub has a dual push and pull operation, where activities and objects can be stored in MUST, MAY, and SHOULD used in the RFC-2119 sense where they appear in CAPITAL LETTERS. Supporting instances MUST indicate their support of this FEP by including its namespace in the As used in this document: CURIE prefixes used in this document: - The prefixes are assumed to be declared in the Supporting software MUST support the following behavior specified in other FEPs - FEP-7628 - FEP-8b32 - specifically the software must be capable of managing keypairs per actor rather than use one private key per instance. All behavior described in this FEP MUST follow receipt of a valid After an Actor \"Supported actor collections\" is left purposefully undefined and is left as an implementation detail to supporting software, however supporting software SHOULD attempt to support all collections and objects that it has a representation for. If unknown object types are encountered, the target instance MAY store a full copy of those objects in case a future software update supports them. If applicable, objects that control the visibility of other objects (in particular: During collection enumeration, requests MUST be signed as being on behalf of the target Actor if signing a request as being on behalf of an actor is supported by the source and target instances. Created/updated timestamps MUST be preserved from the original object, but supporting instances MAY add and MAY display additional properties to indicate an object has been migrated: - a The target instance MUST insert the newly migrated objects in relevant collections and perform any side effects that normally accompany object creation, however it SHOULD NOT emit Activities indicating the creation of these migrated objects. Instead, the target instance MUST create two collections on the Actor object to support gradual migration: The target instance MUST create a The The The items in the Items in the The Implementing software MAY provide appropriate options and interfaces to allow the migrating actor to select which, if any objects they want to migrate (see Pre-Move Moderation). If the actor elects to not migrate any objects, the Upon completion of the ingest routine, the A target instance MUST NOT begin another actor move to another instance until the migration is complete in order to prevent loss of objects still in the process of being migrated from the source instance. (This section is non-normative) (\"...\": \"...\" is used to indicate \"the rest of the object\" in the absence of JSON comments) Actor on target instance The The If the actor is not inlined in a signed The Consuming instances MAY consider a hash of the canonicalized form of the collection (e.g. RFC 8785, RDF canonicalization) as a content hash for the purposes of change detection, but the details of content addressing activitypub objects are outside the scope of this FEP. The (This section is non-normative) In circumstances where the original objects are not available at the source instance, importing objects from some external backup of the relevant collections is no different than enumerating them from the source instance. If a valid Recall this FEP assumes that a valid, signed Target instances MAY treat imports from exported collections as subject to the same moderation flow they would apply to objects imported from a source instance (see Pre-Move Moderation) and choose objects to remove or modify from an import. If moderation is performed, the target instance MUST notify the target actor of removed or modified objects. See notes on security below re: source object forgery. The target instance is the only instance that can know the rate at which it can service requests. Accordingly, the target instance SHOULD use standard HTTP rate limit headers and response codes to coordinate gradual migration requests by 3rd-party instances. The target instance SHOULD service migration-related requests as quickly as possible without otherwise degrading normal service so that time spent with third-party instances in an inconsistent state is minimized. Comparatively little needs to be implemented on the source server. If authentication and signing requests as being made on behalf of an actor is supported by the source and target instances, the source server MUST consider requests made on behalf of the of the target Actor as if they were made by the source Actor for purposes of visibility and access. Source instances MUST make objects that control object visibility (e.g., blocks) available to target instances during migration in order to preserve Actor visibility preferences. If any such visibility control objects are not typically present in an Actor object's linked Collections, the source instance MUST include them in Collections linked from the Actor object, and they SHOULD use keys that can be predicted from the object type, e.g. Source instances SHOULD: - continue to host original copies of the migrated objects until the target instance completes its ingestion. - prompt source actors to export any objects that were rejected or changed during moderation (see Pre-Move Moderation), and attempt to store them for some reasonable period to allow an export to be made. - consume the However, source instances MAY elect not to preserve the original objects for one of several reasons e.g. in FEP-73cd re: moderation, legal, or other concerns that make continued hosting of the source objects intolerable to the source instance. This FEP is written to minimize the responsibility of the source instance, purposefully avoiding binding it into longterm service requirements by making a self-contained record of After observing and validating an account If the If the 3rd-Party instances MUST validate the object integrity proof for actor If after observing a valid For an actor For each entry in the 3rd-party instances MUST NOT emit additional 3rd-party instances SHOULD NOT fetch items referred to in the 3rd-party instances SHOULD implement migration routine operations as optimistic: they should perform predictable operations using their local representations of objects without waiting for full data from the target instance when possible. For example, after consuming the 3rd-party instances SHOULD treat interactions (replies, likes, etc.) from other 3rd-party instances as if they have been, or will be migrated as well. 3rd-party instances SHOULD store the relevant Migration routine operations SHOULD also be patient: requests for Migration of extended objects that are referred to by a migrated object SHOULD be performed on a \"best effort\" basis - when there is an unambiguous change to be made (e.g. updating the The target instance MAY provide some batched or condensed collection of If 3rd-party instances passively or periodically refreshes objects from the source or target instance and observe that they have been migrated (e.g. the source instance responds with a 301 redirect to the target object, a As this FEP is implemented and deployed by various fedi server apps, different instances will \"become aware of\" migrations at different times. This FEP supports incremental migration as each different role becomes object migration aware: See discussion below (Discussion: Moderation) motivating this section and its design. Recall that a valid actor Prior to emitting the publicly-addressed actor Collections or OrderedCollections referenced within the attachments of the Proposed If a valid Actor Move activity is received without having previously accepted a Proposed Move, e.g. from a source instance that does not support this FEP: - If a target instance chooses not to moderate migrated objects, it MUST accept the Actor move activity as otherwise described in this FEP, migrating all objects it is capable of migrating. - If a target instance chooses to moderate migrated objects, it MUST consider all objects it is capable of migrating as being subject to moderation, but MAY choose not to immediately ingest all objects, instead gradually ingest and add them to the If the target instance refuses the initial Proposed (This section is non-normative) Proposed Move with a set of proposed object to migrate
diff --git a/fep/8b32/index.html b/fep/8b32/index.html
index 6b52a97..5c10eaf 100644
--- a/fep/8b32/index.html
+++ b/fep/8b32/index.html
@@ -889,6 +889,7 @@
FEP-ee3a: Exif metadata support
-![]()
-
+#752
Discussions
2026-01-13
Use cases¶
diff --git a/fep/ee3a/index.html b/fep/ee3a/index.html
index ec6b3c4..30d59b7 100644
--- a/fep/ee3a/index.html
+++ b/fep/ee3a/index.html
@@ -647,6 +647,7 @@
Authors
Status
Date received
+Tracking issue
Discussions
Repository
@@ -656,6 +657,7 @@
Marcin Czachurski mczachurski@icloud.com
DRAFT2026-01-13
+#752
Discussions
codeberg
diff --git a/search/search_index.json b/search/search_index.json
index a6e2c8b..6994d43 100644
--- a/search/search_index.json
+++ b/search/search_index.json
@@ -1 +1 @@
-{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"Fediverse Enhancement Proposals","text":"
$ echo -n \"The title of my proposal\" | sha256sum | cut -c-4\nb3f0\n
fep/ using the identifier you just computed.
abcd, then your file would be located at fep/abcd/fep-abcd.md and your frontmatter would include slug: \"abcd\".dateReceived field in the frontmatter to the current date.discussionsTo field containing the URL of the discussion topic to the front matter of your FEP../scripts/new_proposal.py TITLE OF YOUR PROPOSAL\nmigration Collection #702 Discussions 2025-10-09 FEP-d8c8: BitTorrent Torrent Objects #722 Discussions 2025-11-03 FEP-19b3: Specifying Properties of a Service #719 Discussions 2025-11-04 FEP-22b6: Linking an ActivityPub Object to a HTML page and back #730 Discussions 2025-11-12 FEP-f15d: Context Relocation and Removal #751 Discussions 2026-01-12 FEP-ee3a: Exif metadata support - Discussions 2026-01-13"},{"location":"final/","title":"Final","text":"Title Tracking issue Discussions Received Finalized FEP-a4ed: The Fediverse Enhancement Proposal Process #201 - 2020-10-16 2021-01-18 FEP-8fcf: Followers collection synchronization across servers #6 - 2020-10-24 2022-02-07 FEP-f1d5: NodeInfo in Fediverse Software #50 - 2020-12-13 2023-06-02 FEP-400e: Publicly-appendable ActivityPub collections #5 - 2021-02-16 2022-02-04 FEP-e232: Object Links #14 Discussions 2022-08-01 2023-12-03 FEP-1b12: Group federation #22 - 2022-11-12 2023-02-09 FEP-521a: Representing actor's public keys #130 Discussions 2023-07-08 2025-06-14 FEP-67ff: FEDERATION.md #157 Discussions 2023-09-05 2024-09-22 FEP-d556: Server-Level Actor Discovery Using WebFinger #243 - 2024-01-20 2025-03-15 FEP-ae0c: Fediverse Relay Protocols: Mastodon and LitePub #424 Discussions 2024-10-19 2025-03-14"},{"location":"withdrawn/","title":"Withdrawn","text":"Title Tracking issue Discussions Received Withdrawn FEP-8c3f: Web Monetization #3 - 2022-01-18 2023-10-27 FEP-2100: Unbound Group and Organization #36 Discussions 2022-03-31 2025-04-06 FEP-5624: Per-object reply control policies #18 Discussions 2022-08-23 2025-06-24 FEP-cb76: Content Addressed Vocabulary #41 Discussions 2022-11-29 2025-04-07 FEP-2e40: The FEP Vocabulary Extension Process #62 - 2023-02-13 2023-09-04 FEP-d767: Extend ActivityPub with Valueflows #76 - 2023-04-02 2024-02-03 FEP-5bf0: Collection sorting and filtering #80 - 2023-04-10 2025-06-14 FEP-612d: Identifying ActivityPub Objects through DNS #89 - 2023-04-18 2025-10-10 FEP-07d7: A Custom URL Scheme and Web-Based Protocol Handlers for Linking to ActivityPub Resources #171 - 2023-09-22 2025-04-22 FEP-6481: Specifying ActivityPub extension support with NodeInfo #279 - 2024-03-12 2024-10-31 FEP-c7d3: Ownership #323 Discussions 2024-06-04 2025-02-21"},{"location":"fep/0151/","title":"FEP-0151: NodeInfo in Fediverse Software (2025 edition)","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT implementation 2025-05-12 #599 Discussions codeberg"},{"location":"fep/0151/#summary","title":"Summary","text":"usage object are optional and could be omitted to hide usage statistics. Note that users property is not optional:{\n \"usage\": {\n \"users\": {}\n }\n}\nsoftware field and MUST treat software.name and software.version values as opaque strings.metadata","text":"metadata object are not standardized, but some of them are widely used:
nodeName (string): The name of the server.nodeDescription (string): The long form description of the server.staffAccounts (array[string]): The list of staff account IDs (actor IDs for ActivityPub).federation.enabled (boolean): Whether the server is connected to other servers.
"},{"location":"fep/0151/#implementations","title":"Implementations","text":"{\n \"metadata\": {\n \"nodeName\": \"My server\",\n \"nodeDescription\": \"My server\",\n \"staffAccounts\": [\n \"https://social.example/actors/1\"\n ],\n \"federation\": {\n \"enabled\": true\n }\n }\n}\n
"},{"location":"fep/0151/#references","title":"References","text":"
"},{"location":"fep/0151/#copyright","title":"Copyright","text":"DRAFT 2024-04-29 #310 - codeberg"},{"location":"fep/0391/#summary","title":"Summary","text":"
outbox (ActivityPub)inbox (ActivityPub)followers (ActivityPub)following (ActivityPub)liked (ActivityPub)likes (ActivityPub)shares (ActivityPub)replies (FEP-7458)context (FEP-7888)replies and/or context carries additional semantic meaning and is a socially loaded relation used to establish conversational constructs.outbox","text":"<A>'s actor (<A>.actor) is actor <B> Inverse claim : Actor <B>'s outbox collection (<B>.outbox) contains activity <A>inbox","text":"<A> has to/cc/audience including actor <B> Inverse claim : Actor <B>'s inbox collection (<B>.inbox) contains activity <A><B> is addressed directly and not through some collection that triggers inbox forwarding.followers","text":"<B>'s following collection (<B>.following) contains actor <A><B> claims that <B> is following <A><B> claims that <A> is followed by <B><A>'s followers collection (<A>.followers) contains actor <B><A> claims that <A> is followed by <B><A> claims that <B> is following <A>
"},{"location":"fep/0391/#following-claim","title":"Verifying presence in <A>.followers includes <B> as well
attributedTo is <A>subject is <B>relationship is IsFollowingobject is <A>
attributedTo is <A>subject is <A>relationship is IsFollowedByobject is <B>
actor is <A>type is Acceptobject.actor is <B>object.type is Followobject.object is <A>
actor is <A>type is Addobject is <B>target is <A>.followersfollowing","text":"<B>'s followers collection (<B>.followers) contains actor <A><B> claims that <B> is followed by <A><B> claims that <A> is following <B><A>'s following collection (<A>.following) contains actor <B><A> claims that <A> is following <B><A> claims that <B> is followed by <A>
"},{"location":"fep/0391/#liked-claim","title":"Verifying presence in <A>.following includes <B> as well
attributedTo is <A>subject is <A>relationship is IsFollowingobject is <B>
attributedTo is <A>subject is <B>relationship is IsFollowedByobject is <A>
actor is <B>type is Acceptobject.actor is <A>object.type is Followobject.object is <B>object is a trusted activity
actor is <A>type is Addobject is <B>target is <A>.followingliked","text":"<A>'s liked collection (<A>.liked) contains object <O><O>.likes where: - actor is <A> - type is Like - object is <O><A> issues multiple Like activities for the same object <O>, and then issues any Undo Like activities at a later point in time. The most recent activity will have its side-effects carried out. It is possible for some of these functionally duplicate Like activites to remain in <O>.likes even though the object <O> is no longer in <A>.liked. See https://github.com/w3c/activitypub/issues/381 for more information.likes","text":"<O>'s likes collection (<O>.likes) contains a Like activity <L> from actor <A><L> is not already trusted via some other mechanism, this can be verified by showing that <A>.liked includes <O>. Note the duplication issue from the previous section.<L> MAY result in an Add activity <R> targeting <O>.likes. This result can be used by <A> as proof of the claim, if it can be shown to be a trusted activity.shares","text":"<O>'s shares collection (<O>.shares) contains an Announce activity <S> from actor <A><S> MAY result in an Add activity <R> targeting <O>.shares. This result can be used by <A> as proof of the claim, if it can be shown to be a trusted activity.replies","text":"<R> is inReplyTo object <O><O>'s replies collection (<O>.replies) contains object <R> attributed to actor <A>actor is <O>.attributedTo - type set includes Add - object is <R> - target is <O>.repliescontext","text":"<O> has a context pointing to context collection <C><C> contains object <O>actor is <O>.context.attributedTo - type set includes Add - object is <R> - target is <O>.contextresult property provided that its subject is an activity and its referent is a trusted activity. We may also define some extension properties for non-activity objects.replies collections via a reply stamp","text":"replies collection.inReplyTo has been set, the proof can be verified if all of the following are satisfied:
result.actor MUST be included in either inReplyTo.actor or inReplyTo.attributedToresult.type MUST include Addresult.object MUST be equivalent to the current activityresult.target MUST be equivalent to inReplyTo.repliesCreate activity where the object has inReplyTo set, the proof can be verified if all of the following are satisfied:
result.actor MUST be included in either object.inReplyTo.attributedTo or object.inReplyTo.actorresult.type MUST include Addresult.object MUST be equivalent to objectresult.target MUST be equivalent to object.inReplyTo.repliesinReplyTo has been set, we define the extension property inReplyToProof since the use of result is invalid on non-Activity types. The proof is valid if all of the following are satisfied:
inReplyToProof.actor MUST be included in either inReplyTo.attributedTo or inReplyTo.actorinReplyToProof.type MUST include AddinReplyToProof.object MUST be equivalent to the current objectinReplyToProof.target MUST be equivalent to inReplyTo.repliesinReplyTo set to some activity with a replies collection:{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-activity\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Activity\",\n \"object\": \"https://example.com/some-object\",\n \"inReplyTo\": {\n \"id\": \"https://example.com/some-other-create\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Create\",\n \"object\": \"https://example.com/some-other-object\",\n \"content\": \"I am accepting replies to this activity.\",\n \"replies\": \"https://example.com/some-other-create/replies\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-activity\",\n \"target\": \"https://example.com/some-other-create/replies\",\n \"attributedTo\": \"https://example.com/some-activity\"\n }\n}\nCreate activity with object.inReplyTo set to some non-activity object with a replies collection:
"},{"location":"fep/0391/#context-stamp","title":"Showing inclusion in {\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/create-some-reply\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Create\",\n \"object\": {\n \"id\": \"https://example.com/some-reply\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/actors/2\",\n \"content\": \"This is a reply, and I can prove it was added to the replies collection.\",\n \"inReplyTo\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/actors/1\",\n \"content\": \"I am accepting replies to this object.\",\n \"replies\": \"https://example.com/some-object/replies\"\n },\n \"inReplyToProof\": \"https://example.com/some-proof\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-reply\",\n \"target\": \"https://example.com/some-object/replies\",\n \"attributedTo\": \"https://example.com/create-some-reply\"\n }\n}\ncontext collections via a context stamp","text":"context collection.context has been set to a collection, the proof can be verified if all of the following are satisfied:
result.actor MUST be included in context.attributedToresult.type MUST include Addresult.object MUST be equivalent to the current activityresult.target MUST be equivalent to contextCreate activity where the object has context set, the proof can be verified if all of the following are satisfied:
result.actor MUST be included in object.context.attributedToresult.type MUST include Addresult.object MUST be equivalent to objectresult.target MUST be equivalent to object.contextcontext has been set, we define the extension property contextProof since the use of result is invalid on non-Activity types. The proof is valid if all of the following are satisfied:
contextProof.actor MUST be included in context.attributedTocontextProof.type MUST include AddcontextProof.object MUST be equivalent to the current objectcontextProof.target MUST be equivalent to contextcontext set to some owned collection:{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-activity\",\n \"actor\": \"https://example.com/some-actor\",\n \"type\": \"Activity\",\n \"object\": \"https://example.com/some-object\",\n \"context\": {\n \"id\": \"https://example.com/some-context\",\n \"type\": \"Collection\",\n \"attributedTo\": \"https://example.com/some-context-moderator\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/some-context-moderator\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-activity\",\n \"target\": \"https://example.com/some-context\",\n \"attributedTo\": \"https://example.com/some-activity\"\n }\n}\nCreate activity with object.context set to some owned collection:
"},{"location":"fep/0391/#likes-stamp","title":"Showing inclusion in {\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/create-some-object\",\n \"actor\": \"https://example.com/some-actor\",\n \"type\": \"Create\",\n \"object\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/some-actor\",\n \"content\": \"This object is part of some context, and I can prove it was added to the context collection.\",\n \"context\": {\n \"id\": \"https://example.com/some-context\",\n \"type\": \"Collection\",\n \"attributedTo\": \"https://example.com/some-context-moderator\"\n },\n \"contextProof\": \"https://example.com/some-proof\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/some-context-moderator\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-object\",\n \"target\": \"https://example.com/some-context\",\n \"attributedTo\": \"https://example.com/create-some-object\"\n }\n}\nlikes collections via a like stamp","text":"object.likes collection.Like activity where the object has a likes collection, the proof can be verified if all of the following are satisfied:
result.actor MUST be included in object.attributedToresult.type MUST include Addresult.object MUST be equivalent to the current activityresult.target MUST be equivalent to object.likes
"},{"location":"fep/0391/#shares-stamp","title":"Showing inclusion in {\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-like\",\n \"summary\": \"A Like activity, with proof that it was added to the likes collection.\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Like\",\n \"object\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"content\": \"I am accepting likes of this object.\",\n \"likes\": \"https://example.com/some-object/likes\",\n \"attributedTo\": \"https://example.com/actors/1\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-like\",\n \"target\": \"https://example.com/some-object/likes\",\n \"attributedTo\": \"https://example.com/some-like\"\n }\n}\nshares collections via a share stamp","text":"object.shares collection.Announce activity where the object has a shares collection, the proof can be verified if all of the following are satisfied:
result.actor MUST be included in object.attributedToresult.type MUST include Addresult.object MUST be equivalent to the current activityresult.target MUST be equivalent to object.shares
"},{"location":"fep/0391/#relationship-proofs","title":"Relationship proofs","text":"{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-announce\",\n \"summary\": \"An Announce activity, with proof that it was added to the shares collection.\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Announce\",\n \"object\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"content\": \"I am accepting shares of this object.\",\n \"shares\": \"https://example.com/some-object/shares\",\n \"attributedTo\": \"https://example.com/actors/1\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-announce\",\n \"target\": \"https://example.com/some-object/shares\",\n \"attributedTo\": \"https://example.com/some-announce\"\n }\n}\nrelationshipProof since the use of result is invalid on non-Activity types. The proof is valid if all of the following are satisfied:{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/some-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/1\",\n \"subject\": {\n \"id\": \"https://example.com/actors/1\",\n \"following\": \"https://example.com/actors/1/following\"\n },\n \"relationship\": \"IsFollowing\",\n \"object\": {\n \"id\": \"https://example.com/actors/2\",\n \"followers\": \"https://example.com/actors/2/followers\"\n },\n \"relationshipProof\": [\n {\n \"id\": \"https://example.com/not-enough-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/2\",\n \"target\": \"https://example.com/actors/1/following\"\n },\n {\n \"id\": \"https://example.com/proof-by-inverse-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/2\",\n \"subject\": \"https://example.com/actors/2\",\n \"relationship\": \"IsFollowedBy\",\n \"object\": \"https://example.com/actors/1\"\n },\n {\n \"id\": \"https://example.com/proof-by-being-added-to-followers\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/1\",\n \"target\": \"https://example.com/actors/2/followers\"\n },\n {\n \"id\": \"https://example.com/proof-by-having-follow-accepted\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Accept\",\n \"object\": {\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Follow\",\n \"object\": \"https://example.com/actors/2\"\n }\n }\n ]\n}\n{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/some-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/1\",\n \"subject\": {\n \"id\": \"https://example.com/actors/1\",\n \"followers\": \"https://example.com/actors/1/following\"\n },\n \"relationship\": \"IsFollowedBy\",\n \"object\": {\n \"id\": \"https://example.com/actors/2\",\n \"following\": \"https://example.com/actors/2/followers\"\n },\n \"relationshipProof\": [\n {\n \"id\": \"https://example.com/not-enough-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/2\",\n \"target\": \"https://example.com/actors/1/followers\"\n },\n {\n \"id\": \"https://example.com/proof-by-inverse-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/2\",\n \"subject\": \"https://example.com/actors/2\",\n \"relationship\": \"IsFollowing\",\n \"object\": \"https://example.com/actors/1\"\n },\n {\n \"id\": \"https://example.com/proof-by-being-added-to-following\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/1\",\n \"target\": \"https://example.com/actors/2/following\"\n },\n {\n \"id\": \"https://example.com/proof-by-having-follow\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Follow\",\n \"object\": \"https://example.com/actors/1\"\n }\n ]\n}\n
"},{"location":"fep/0391/#root-trust","title":"Existing mechanisms of trust","text":"{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-follow\",\n \"actor\": {\n \"id\": \"https://example.com/actors/2\",\n \"following\": \"https://example.com/actors/2/following\"\n },\n \"type\": \"Follow\",\n \"object\": {\n \"id\": \"https://example.com/actors/1\",\n \"followers\": \"https://example.com/actors/1/followers\"\n },\n \"result\": {\n \"id\": \"https://example.com/accept-follow\",\n \"actor\": \"https://example.com/actor/1\",\n \"type\": \"Accept\",\n \"object\": \"https://example.com/some-follow\",\n \"result\": [\n {\n \"id\": \"https://example.com/resulting-add-to-followers\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/2\",\n \"target\": \"https://example.com/actors/1/followers\",\n \"attributedTo\": \"https://example.com/accept-follow\"\n },\n {\n \"id\": \"https://example.com/resulting-add-to-following\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/1\",\n \"target\": \"https://example.com/actors/2/following\",\n \"attributedTo\": \"https://example.com/accept-follow\"\n }\n ]\n }\n}\n
"},{"location":"fep/0391/#flow","title":"Obtaining and revoking stamps","text":"Add activity representing this side-effect. The ActivityPub server MAY require manual action by a user. The resulting stamp activity SHOULD be attributedTo the activity that caused the side-effect, delivered to its actor, and additionally SHOULD either be resolvable (to allow direct same-origin checking) or otherwise include a cryptographic proof. If a cryptographic proof is included, the proof SHOULD expire after some reasonable window, beyond which point a new proof should be issued. If the resulting stamp activity is resolvable, it MAY become unresolvable after some time; HTTP caching SHOULD be used to indicate a time-to-live for ActivityPub clients to re-check cached stamps. The time-to-live MAY be used as the duration of the cryptographic proof, if one exists.Update their object to include the appropriate property for the stamp -- result if it is an activity, inReplyToProof if it is a non-activity object that declares inReplyTo, contextProof if it is a non-activity object that declares context. [TODO: how to handle relationship proofs?]Update the stamp with some property to signal that the Add activity has been undone. [TODO: flesh this out more -- how does this work exactly? should it use Remove? Tombstone? Undo? how does this interact with outbox and the activity history? i'm thinking Update -> Tombstone, or extension like \"undoneBy\"]result on an activity -- Like, Announce, Activity with inReplyTo, Activity with context.{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-like\",\n \"summary\": \"Actor 2 liked a Note by actor 1\",\n \"actor\": {\n \"id\": \"https://example.com/actors/2\",\n \"followers\": \"https://example.com/actors/2/followers\"\n },\n \"type\": \"Like\",\n \"object\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"content\": \"I am accepting likes of this object.\",\n \"likes\": \"https://example.com/some-object/likes\",\n \"attributedTo\": {\n \"id\": \"https://example.com/actors/1\",\n \"followers\": \"https://example.com/actors/1/followers\"\n },\n \"cc\": [\n \"https://example.com/actors/1/followers\",\n \"as:Public\"\n ],\n \"audience\": \"https://example.com/some-object/audience\"\n },\n \"to\": \"https://example.com/actors/1\",\n \"cc\": [\n \"https://example.com/actors/2/followers\",\n \"https://example.com/some-object/audience\",\n \"as:Public\"\n ],\n \"audience\": \"https://example.com/some-like/audience\"\n}\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-proof\",\n \"summary\": \"Actor 1 approved a like\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-like\",\n \"target\": \"https://example.com/some-object/likes\",\n \"attributedTo\": \"https://example.com/some-like\",\n \"to\": \"https://example.com/actors/2\",\n \"cc\": [\n \"https://example.com/actors/2/followers\",\n \"https://example.com/some-like/audience\",\n \"as:Public\"\n ]\n}\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-update\",\n \"summary\": \"Actor 2 updated their Like with proof\",\n \"actor\": {\n \"id\": \"https://example.com/actors/2\",\n \"followers\": \"https://example.com/actors/2/followers\"\n },\n \"type\": \"Update\",\n \"object\": {\n \"id\": \"https://example.com/some-like\",\n \"summary\": \"Actor 2 liked a Note by actor 1, with proof\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Like\",\n \"object\": \"https://example.com/some-object\",\n \"to\": \"https://example.com/actors/1\",\n \"cc\": [\n \"https://example.com/actors/2/followers\",\n \"https://example.com/some-object/audience\",\n \"as:Public\"\n ],\n \"audience\": \"https://example.com/some-like/audience\",\n \"result\": \"https://example.com/some-proof\"\n },\n \"cc\": [\n \"https://example.com/actors/2/followers\",\n \"https://example.com/some-like/audience\",\n \"as:Public\"\n ]\n}\ninReplyToProof or contextProof on the object of a Create instead of using only result on the activity itself.{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/create-some-object\",\n \"actor\": \"https://example.com/some-actor\",\n \"type\": \"Create\",\n \"object\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"attributedTo\": {\n \"id\": \"https://example.com/some-actor\",\n \"followers\": \"https://example.com/some-actor/followers\"\n },\n \"content\": \"This object is part of some context, and I can prove it was added to the context collection.\",\n \"context\": {\n \"id\": \"https://example.com/some-context\",\n \"type\": \"Collection\",\n \"attributedTo\": \"https://example.com/some-context-moderator\",\n \"audience\": \"https://example.com/some-context/audience\"\n },\n \"to\": \"https://example.com/some-context-moderator\",\n \"cc\": \"as:Public\"\n },\n \"to\": [\n \"https://example.com/some-context-moderator\",\n \"https://example.com/some-context/audience\"\n ],\n \"cc\": \"as:Public\",\n \"audience\": \"https://example.com/create-some-object/audience\"\n}\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-proof\",\n \"summary\": \"A new post was added to the context\",\n \"actor\": \"https://example.com/some-context-moderator\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-object\",\n \"target\": \"https://example.com/some-context\",\n \"attributedTo\": \"https://example.com/create-some-object\",\n \"cc\": [\n \"https://example.com/some-context/audience\",\n \"https://example.com/some-actor\",\n \"https://example.com/create-some-object/audience\",\n \"as:Public\"\n ]\n}\nresult, and one for the object of that activity to add object.contextProof:{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/update-some-object\",\n \"actor\": {\n \"id\": \"https://example.com/some-actor\",\n \"followers\": \"https://example.com/some-actor/followers\"\n },\n \"type\": \"Update\",\n \"object\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/some-actor\",\n \"content\": \"This object is part of some context, and I can prove it was added to the context collection.\",\n \"context\": \"https://example.com/some-context\",\n \"contextProof\": \"https://example.com/some-proof\",\n \"to\": \"https://example.com/some-context-moderator\",\n \"cc\": \"as:Public\"\n },\n \"to\": [\n \"https://example.com/some-actor/followers\",\n \"as:Public\"\n ]\n}\n
"},{"location":"fep/0391/#references","title":"References","text":"{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/update-create\",\n \"actor\": \"https://example.com/some-actor\",\n \"type\": \"Update\",\n \"object\": {\n \"id\": \"https://example.com/create-some-object\",\n \"actor\": \"https://example.com/some-actor\",\n \"type\": \"Create\",\n \"object\": \"https://example.com/some-object\",\n \"to\": \"https://example.com/some-context-moderator\",\n \"cc\": \"as:Public\",\n \"result\": \"https://example.com/some-proof\"\n }\n}\n
"},{"location":"fep/0391/#copyright","title":"Copyright","text":"context collection
"},{"location":"fep/0391/inReplyToProof/","title":"inReplyToProof https://w3id.org/fep/0391/inReplyToProof","text":"{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/create-some-object\",\n \"actor\": \"https://example.com/some-actor\",\n \"type\": \"Create\",\n \"object\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/some-actor\",\n \"content\": \"This object is part of some context, and I can prove it was added to the context collection.\",\n \"context\": {\n \"id\": \"https://example.com/some-context\",\n \"type\": \"Collection\",\n \"attributedTo\": \"https://example.com/some-context-moderator\"\n },\n \"contextProof\": \"https://example.com/some-proof\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/some-context-moderator\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-comment\",\n \"target\": \"https://example.com/some-context\",\n \"attributedTo\": \"https://example.com/create-some-object\"\n }\n}\n
"},{"location":"fep/0391/relationshipProof/","title":"relationshipProof https://w3id.org/fep/0391/relationshipProof","text":"{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/create-some-reply\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Create\",\n \"object\": {\n \"id\": \"https://example.com/some-reply\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/actors/2\",\n \"content\": \"This is a reply, and I can prove it was added to the replies collection.\",\n \"inReplyTo\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/actors/1\",\n \"content\": \"I am accepting replies to this object.\",\n \"replies\": \"https://example.com/some-object/replies\"\n },\n \"inReplyToProof\": \"https://example.com/some-proof\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-reply\",\n \"target\": \"https://example.com/some-object/replies\",\n \"attributedTo\": \"https://example.com/create-some-reply\"\n }\n}\n{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/some-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/1\",\n \"subject\": {\n \"id\": \"https://example.com/actors/1\",\n \"following\": \"https://example.com/actors/1/following\"\n },\n \"relationship\": \"IsFollowing\",\n \"object\": {\n \"id\": \"https://example.com/actors/2\",\n \"followers\": \"https://example.com/actors/2/followers\"\n },\n \"relationshipProof\": [\n {\n \"id\": \"https://example.com/not-enough-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/2\",\n \"target\": \"https://example.com/actors/1/following\"\n },\n {\n \"id\": \"https://example.com/proof-by-inverse-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/2\",\n \"subject\": \"https://example.com/actors/2\",\n \"relationship\": \"IsFollowedBy\",\n \"object\": \"https://example.com/actors/1\"\n },\n {\n \"id\": \"https://example.com/proof-by-being-added-to-followers\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/1\",\n \"target\": \"https://example.com/actors/2/followers\"\n },\n {\n \"id\": \"https://example.com/proof-by-having-follow-accepted\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Accept\",\n \"object\": {\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Follow\",\n \"object\": \"https://example.com/actors/2\"\n }\n }\n ]\n}\n
"},{"location":"fep/03c1/","title":"FEP-03c1: Actors without acct-URI","text":"Authors Status Date received Tracking issue Discussions Repository helge @helge@mymath.rocks {\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/some-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/1\",\n \"subject\": {\n \"id\": \"https://example.com/actors/1\",\n \"followers\": \"https://example.com/actors/1/following\"\n },\n \"relationship\": \"IsFollowedBy\",\n \"object\": {\n \"id\": \"https://example.com/actors/2\",\n \"following\": \"https://example.com/actors/2/followers\"\n },\n \"relationshipProof\": [\n {\n \"id\": \"https://example.com/not-enough-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/2\",\n \"target\": \"https://example.com/actors/1/followers\"\n },\n {\n \"id\": \"https://example.com/proof-by-inverse-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/2\",\n \"subject\": \"https://example.com/actors/2\",\n \"relationship\": \"IsFollowing\",\n \"object\": \"https://example.com/actors/1\"\n },\n {\n \"id\": \"https://example.com/proof-by-being-added-to-following\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/1\",\n \"target\": \"https://example.com/actors/2/following\"\n },\n {\n \"id\": \"https://example.com/proof-by-having-follow\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Follow\",\n \"object\": \"https://example.com/actors/1\"\n }\n ]\n}\nDRAFT 2023-11-10 #205 - codeberg"},{"location":"fep/03c1/#summary","title":"Summary","text":"acct:user@domain.example as @user@domain.example. This FEP states that if there is no acct-URI associated with an actor, the actor should be displayed as its id. So the actor with id https://actor.example/path will be displayed as https://actor.example/path.https://rss.example/actor?feed=https%3A%2F%2Fpypi.org%2Frss%2Fproject%2Fbovine%2Freleases.xml\nrss.example to automatically generate the respective actor and generate an actor object like{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://rss.example/actor?feed=https%3A%2F%2Fpypi.org%2Frss%2Fproject%2Fbovine%2Freleases.xml\",\n \"type\": \"Service\",\n \"inbox\": \"https://rss.example/inbox?feed=https%3A%2F%2Fpypi.org%2Frss%2Fproject%2Fbovine%2Freleases.xml\",\n \"outbox\": \"https://rss.example/outbox?feed=https%3A%2F%2Fpypi.org%2Frss%2Fproject%2Fbovine%2Freleases.xml\",\n \"followers\": \"https://rss.example/followers?feed=https%3A%2F%2Fpypi.org%2Frss%2Fproject%2Fbovine%2Freleases.xml\",\n \"name\": \"PyPI recent updates for bovine\",\n \"url\": \"https://pypi.org/project/bovine/\",\n \"summary\": \"Recent updates to the Python Package Index for bovine\"\n}\npreferredUsername and thus acct-URI. If one wanted to create one, the best choice would probably be to hash the url of the feed. This unfortunately has the consequence of not generating a human readable id, but quite the opposite.DRAFT 2025-04-03 #547 Discussions codeberg"},{"location":"fep/044f/#summary","title":"Summary","text":"Follow and Accept, approval can be granted automatically depending on the user's choice.interactionPolicy vocabulary directly comes from GoToSocial's interaction policies which have since evolved along the current proposal.quote (https://w3id.org/fep/044f#quote) attribute.{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"quote\": {\n \"@id\": \"https://w3id.org/fep/044f#quote\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"type\": \"Note\",\n \"id\": \"https://example.com/users/bob/statuses/1\",\n \"attributedTo\": \"https://example.com/users/bob\",\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\",\n \"https://example.com/users/alice\"\n ],\n \"content\": \"I am quoting alice's post<span class=\\\"quote-inline\\\"><br/>RE: <a href=\\\"https://example.com/users/alice/statuses/1\\\">https://example.com/users/alice/statuses/1</a></span>\",\n \"quote\": \"https://example.com/users/alice/statuses/1\"\n}\n<span class=\\\"quote-inline\\\"><br/>RE: <a href=\\\"https://example.com/users/alice/statuses/1\\\">https://example.com/users/alice/statuses/1</a></span> part of the content is an example of textual fallback, but does not otherwise carry meaning. In particular, it does not influence where the embedded quote should be displayed.https://w3id.org/fep/044f#quote, there are competing definitions for the representation of quote posts: - _misskey_quote (https://misskey-hub.net/ns/#_misskey_quote) - quoteUrl (https://www.w3.org/ns/activitystreams#quoteUrl) - quoteUri (http://fedibird.com/ns#quoteUri) - FEP-e232 Object links with a https://misskey-hub.net/ns/#_misskey_quote rel valuehttps://w3id.org/fep/044f#quote.
"},{"location":"fep/044f/#advertising-a-quote-policy","title":"Advertising a quote policy","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"quoteUrl\": \"as:quoteUrl\",\n \"quoteUri\": \"http://fedibird.com/ns#quoteUri\",\n \"_misskey_quote\": \"https://misskey-hub.net/ns/#_misskey_quote\",\n \"quote\": {\n \"@id\": \"https://w3id.org/fep/044f#quote\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"type\": \"Note\",\n \"id\": \"https://example.com/users/bob/statuses/1\",\n \"attributedTo\": \"https://example.com/users/bob\",\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\",\n \"https://example.com/users/alice\"\n ],\n \"content\": \"I am quoting alice's post<span class=\\\"quote-inline\\\"><br/>RE: <a href=\\\"https://example.com/users/alice/statuses/1\\\">https://example.com/users/alice/statuses/1</a></span>\",\n \"quote\": \"https://example.com/users/alice/statuses/1\",\n \"quoteUrl\": \"https://example.com/users/alice/statuses/1\",\n \"quoteUri\": \"https://example.com/users/alice/statuses/1\",\n \"_misskey_quote\": \"https://example.com/users/alice/statuses/1\",\n \"tag\": [\n {\n \"type\": \"Link\",\n \"mediaType\": \"application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"\",\n \"rel\": \"https://misskey-hub.net/ns#_misskey_quote\",\n \"href\": \"https://example.com/users/alice/statuses/1\"\n }\n ]\n}\ncanQuote sub-policy.interactionPolicy (https://gotosocial.org/ns#interactionPolicy) with a canQuote (https://gotosocial.org/ns#canQuote) sub-policy.automaticApproval (https://gotosocial.org/ns#automaticApproval): an array of Actor and Collection of Actor objects from whom interactions are expected to be automatically approved - manualApproval (https://gotosocial.org/ns#manualApproval): an array of Actor and Collection of Actor objects from whom interactions are subject to manual reviewautomaticApproval nor manualApproval are expected to never be approved.interactionPolicy.canQuote.automaticApproval SHOULD contain the object author's identifier as its single value. This is because an empty array is equivalent to a missing property under JSON-LD canonicalization.automaticApproval and manualApproval SHOULD be restricted to individual actors, the special public collection https://www.w3.org/ns/activitystreams#Public, the author's followers collection, and the author's following collection.
"},{"location":"fep/044f/#QuoteAuthorization","title":"Approval stamps","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"gts\": \"https://gotosocial.org/ns#\",\n \"interactionPolicy\": {\n \"@id\": \"gts:interactionPolicy\",\n \"@type\": \"@id\"\n },\n \"canQuote\": {\n \"@id\": \"gts:canQuote\",\n \"@type\": \"@id\"\n },\n \"automaticApproval\": {\n \"@id\": \"gts:automaticApproval\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"interactionPolicy\": {\n \"canQuote\": {\n \"automaticApproval\": \"https://example.com/users/alice/followers\"\n }\n },\n \"type\": \"Note\",\n \"id\": \"https://example.com/users/alice/statuses/1\",\n \"attributedTo\": \"https://example.com/users/alice\",\n \"to\": \"https://www.w3.org/ns/activitystreams#Public\",\n \"content\": \"I allow my followers to quote this post\"\n}\nQuoteAuthorization (https://w3id.org/fep/044f#QuoteAuthorization), with interactingObject (https://gotosocial.org/ns#interactingObject), interactionTarget (https://gotosocial.org/ns#interactionTarget) and attributedTo attributes.interactingObject attribute MUST reference the accepted quote post, the interactionTarget attribute MUST reference the quoted object, and the attributedTo attribute MUST correspond to the author of the quoted object.QuoteAuthorization object MUST be dereferenceable by all parties allowed to see the original post, and MAY be publicly dereferenceable. It MUST NOT embed its interactingObject as to avoid possible information leaks. For the same reason, it MUST NOT embed its interactionTarget object if the server is unable to verify that the party dereferencing the object has permission to see the quoted object.QuoteAuthorization, the interactionTarget MAY be inlined if the third-party has permission to access the quoted object. This is so that the third-party does not have to perform a second request to access the quoted object.QuoteAuthorization","text":"https://example.com/users/alice has accepted https://example.org/users/bob/statuses/1 as a quote of her post https://example.com/users/alice/statuses/1:
"},{"location":"fep/044f/#verifying-a-quoteauthorization","title":"Verifying a {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"QuoteAuthorization\": \"https://w3id.org/fep/044f#QuoteAuthorization\",\n \"gts\": \"https://gotosocial.org/ns#\",\n \"interactingObject\": {\n \"@id\": \"gts:interactingObject\",\n \"@type\": \"@id\"\n },\n \"interactionTarget\": {\n \"@id\": \"gts:interactionTarget\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"type\": \"QuoteAuthorization\",\n \"id\": \"https://example.com/users/alice/stamps/1\",\n \"attributedTo\": \"https://example.com/users/alice\",\n \"interactingObject\": \"https://example.org/users/bob/statuses/1\",\n \"interactionTarget\": \"https://example.com/users/alice/statuses/1\"\n}\nQuoteAuthorization","text":"QuoteAuthorization MUST satisfy the following properties: - its interactingObject is the quote post under consideration - its interactionTarget property is the quoted object - its attributedTo property is the author of its interactionTarget - the authenticity of the QuoteAuthorization object can be assertedQuoteAuthorization","text":"Deleteing the stamp.QuoteRequest request activity","text":"QuoteRequest (https://w3id.org/fep/044f#QuoteRequest) activity type is introduced to request approval for a quote post.QuoteRequest activity uses the object property to refer to the quoted object, and the instrument property to refer to the quote post.QuoteRequest activity","text":"
"},{"location":"fep/044f/#receiving-accepting-or-rejecting-quote-posts","title":"Receiving, accepting or rejecting quote posts","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"QuoteRequest\": \"https://w3id.org/fep/044f#QuoteRequest\",\n \"quote\": {\n \"@id\": \"https://w3id.org/fep/044f#quote\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"type\": \"QuoteRequest\",\n \"id\": \"https://example.com/users/bob/statuses/1/quote\",\n \"actor\": \"https://example.com/users/bob\",\n \"object\": \"https://example.com/users/alice/statuses/1\",\n \"instrument\": {\n \"type\": \"Note\",\n \"id\": \"https://example.com/users/bob/statuses/1\",\n \"attributedTo\": \"https://example.com/users/bob\",\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\",\n \"https://example.com/users/alice\"\n ],\n \"content\": \"I am quoting alice's post<br/>RE: https://example.com/users/alice/statuses/1\",\n \"quote\": \"https://example.com/users/alice/statuses/1\"\n }\n}\nQuoteRequest activity, the original author decides (either manually or automatically) whether the quote is acceptable. Software that automatically accepts quotes on the author's behalf should notify the author of such quotes according to their notification settings.instrument of the QuoteRequest itself to decide whether it is acceptable.Accept activity with the QuoteRequest activity as its object, and a QuoteAuthorization as its result.Reject activity with the QuoteRequest activity as its object.Accept","text":"
"},{"location":"fep/044f/#example-reject","title":"Example {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"QuoteRequest\": \"https://w3id.org/fep/044f#QuoteRequest\"\n }\n ],\n \"type\": \"Accept\",\n \"to\": \"https://example.com/users/bob\",\n \"id\": \"https://example.com/users/alice/activities/1234\",\n \"actor\": \"https://example.com/users/alice\",\n \"object\": {\n \"type\": \"QuoteRequest\",\n \"id\": \"https://example.com/users/bob/statuses/1/quote\",\n \"actor\": \"https://example.com/users/bob\",\n \"object\": \"https://example.com/users/alice/statuses/1\",\n \"instrument\": \"https://example.org/users/bob/statuses/1\"\n },\n \"result\": \"https://example.com/users/alice/stamps/1\"\n}\nReject","text":"
"},{"location":"fep/044f/#quoteAuthorization","title":"Requesting, obtaining and validating approval","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"Quote\": \"https://w3id.org/fep/044f#QuoteRequest\"\n }\n ],\n \"type\": \"Reject\",\n \"to\": \"https://example.com/users/bob\",\n \"id\": \"https://example.com/users/alice/activities/1234\",\n \"actor\": \"https://example.com/users/alice\",\n \"object\": {\n \"type\": \"QuoteRequest\",\n \"id\": \"https://example.com/users/bob/statuses/1/quote\",\n \"actor\": \"https://example.com/users/bob\",\n \"object\": \"https://example.com/users/alice/statuses/1\",\n \"instrument\": \"https://example.org/users/bob/statuses/1\"\n }\n}\nQuoteRequest (https://w3id.org/fep/044f#QuoteRequest) activity to the author of the quoted object, with the quoted object as its object property and the quote post as its instrument.instrument property and, if not, it SHOULD dereferenceable by the recipient at this point, as the author of the quoted object may want to inspect it to decide whether to accept the quote.Accept or Reject activity before sending the post's Create activity to its intended audience. Doing so is possible for ActivityPub servers that implement the current proposal, and avoids having to issue an Update soon afterwards the Create for the quote post. It is however not possible to implement for ActivityPub clients, which will likely need to issue a Create before the QuoteRequest activity.Reject activity from the quoted object's author to their QuoteRequest activity, they MUST consider the quote post to be explicitly rejected.Accept before issuing a Create, this MAY translate as the inability to publish the quote post.Delete to outright remove the quote post, or an Update to remove the quote part from the post.Accept activity, they MUST add a reference to its result in the quoteAuthorization (https://w3id.org/fep/044f#quoteAuthorization) property.Create activity to the quote post's intended audience, they SHOULD send a Create activity or an Update activity with the updated quoteAuthorization property.Note object","text":"
"},{"location":"fep/044f/#verifying-third-party-quote-posts","title":"Verifying third-party quote posts","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"quote\": \"https://w3id.org/fep/044f#quote\",\n \"quoteAuthorization\": {\n \"@id\": \"https://w3id.org/fep/044f#quoteAuthorization\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"type\": \"Note\",\n \"id\": \"https://example.com/users/bob/statuses/1\",\n \"attributedTo\": \"https://example.com/users/bob\",\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\",\n \"https://example.com/users/alice\"\n ],\n \"content\": \"I am quoting alice's post<br/>RE: https://example.com/users/alice/statuses/1\",\n \"quote\": \"https://example.com/users/alice/statuses/1\",\n \"quoteAuthorization\": \"https://example.com/users/alice/stamps/1\"\n}\nattributedTo) - quoteAuthorization exists, can be dereferenced and is a valid QuoteAuthorization activity for this objectDelete the QuoteAuthorization. They SHOULD send the Delete activity to the quote post's author and any recipient it has reasons to think has accessed the quote post.object nor the target of the QuoteAuthorization, so as to avoid potential information leakage.Delete activity for a previously-verified QuoteAuthorization, third-parties MUST check that the Delete is valid and MUST subsequently consider the quote post unapproved.Delete to the audience of the quote post.quoteAuthorization document when an already-known quote post is accessed for the first time in a given period of time.Accept itself. Indeed, nothing in the ActivityPub specification would cause a Reject or Undo activity to invalidate the Accept activity itself, which means it would not be suitable as an approval stamp. While ActivityPub does not technically forbid Accept activities to be the target of a Delete activity, we have found no precedent for that, and we anticipate that deleting activities might not be correctly handled across the fediverse. For this reason, we opted to use a separate object that can be directly managed by an ActivityPub client, for instance by issuing a Create activity ahead of sending the Accept activity, and that can be deleted with the usual mechanism.<span class=\\\"quote-inline\\\"><br/>RE: <a href=\\\"https://example.com/users/alice/statuses/1\\\">https://example.com/users/alice/statuses/1</a></span>.quote-inline can be useful to hide redundant information information when the post is detected to have an attached quote.QuoteAuthorization object, malicious actors could exploit this in a split horizon setting, sending different versions of the same activity to different actors. This is, however, already a concern in pretty much all contexts in ActivityPub, and enshrining that information in the QuoteAuthorization object would have many drawbacks: - significantly more complex implementation - inability to change the JSON-LD representation after the fact - possibly leaking private information if the QuoteAuthorization object is publicly dereferenceable
"},{"location":"fep/044f/#references","title":"References","text":"QuoteAuthorization in response to a QuoteRequest, it does not currently verify quotes nor request stampsas:Public, the author's followers collections, or nobody)
"},{"location":"fep/044f/#copyright","title":"Copyright","text":"DRAFT 2024-09-30 #402 Discussions codeberg"},{"location":"fep/0499/#summary","title":"Summary","text":"bto and bcc more efficiently.multibox endpoint which is available for efficient delivery of activities to multiple inboxes on the same domain.multibox who would otherwise be delivered to individually, and deliver an activity of the following form to their shared multibox endpoint:
Add.object MUST be the activity being delivered.target MUST be all inboxes being delivered to.object into all local inboxes specified in target, although it MAY filter delivery to certain inboxes according to implementation-specific rules (for example, spam filtering).{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/0499\"\n ],\n \"id\": \"https://remote.example/actors/af4c8205cd81\",\n \"type\": \"Person\",\n \"name\": \"Alice P. Hacker\",\n \"inbox\": \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"endpoints\": {\n \"multibox\": \"https://remote.example/multibox\"\n }\n}\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://remote.example/actors/af4c8205cd81\",\n \"type\": \"Person\",\n \"name\": \"Alice P. Hacker\",\n \"inbox\": \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"endpoints\": {\n \"https://w3id.org/fep/0499/multibox\": {\"id\": \"https://remote.example/multibox\"}\n }\n}\nPOST /multibox HTTP/1.1\nHost: remote.example\nContent-Type: application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-activity\",\n \"target\": [\n \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"https://remote.example/inboxes/d21f509146e5\",\n \"https://remote.example/inboxes/68a7453f79e4\",\n \"https://remote.example/inboxes/655216a0be07\",\n \"https://remote.example/inboxes/84907eff485d\",\n ]\n}\n
"},{"location":"fep/0499/#terms-defined","title":"Terms defined","text":"multibox URI POST /multibox HTTP/1.1\nHost: remote.example\nContent-Type: application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Add\",\n \"object\": {\n \"@context\": \"https://context.example\"\n \"actor\": \"https://example.com/some-actor\",\n \"type\": \"InGameNotification\",\n \"content\": \"The payload is nearing the checkpoint!\",\n \"generator\": \"https://game.example\"\n },\n \"target\": [\n \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"https://remote.example/inboxes/d21f509146e5\",\n \"https://remote.example/inboxes/68a7453f79e4\",\n \"https://remote.example/inboxes/655216a0be07\",\n \"https://remote.example/inboxes/84907eff485d\",\n ]\n}\nhttps://w3id.org/fep/0499/multibox Label multibox endpoint Comment An optional endpoint used for wide delivery of activities to multiple inboxes. POST an Add activity where the object is at least one activity to be delivered, and the target is at least one inbox to deliver to. Domain A map of endpoints Range The endpoint (@id) Required No Functional Yes Is defined by FEP-0499 \n
\n{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/0499\"\n ],\n \"id\": \"https://remote.example/actors/af4c8205cd81\",\n \"type\": \"Person\",\n \"name\": \"Alice P. Hacker\",\n \"inbox\": \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"endpoints\": {\n \"multibox\": \"https://remote.example/multibox\"\n }\n}\n\n\n
\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://remote.example/actors/af4c8205cd81\",\n \"type\": \"Person\",\n \"name\": \"Alice P. Hacker\",\n \"inbox\": \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"endpoints\": {\n \"https://w3id.org/fep/0499/multibox\": {\"id\": \"https://remote.example/multibox\"}\n }\n}\n\n\n
"},{"location":"fep/0499/#references","title":"References","text":"\nPOST /multibox HTTP/1.1\nHost: remote.example\nContent-Type: application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-activity\",\n \"target\": [\n \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"https://remote.example/inboxes/d21f509146e5\",\n \"https://remote.example/inboxes/68a7453f79e4\",\n \"https://remote.example/inboxes/655216a0be07\",\n \"https://remote.example/inboxes/84907eff485d\",\n ]\n}\n\n
"},{"location":"fep/0499/#copyright","title":"Copyright","text":"https://w3id.org/fep/0499/multiboxendpoints{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/0499\"\n ],\n \"id\": \"https://remote.example/actors/af4c8205cd81\",\n \"type\": \"Person\",\n \"name\": \"Alice P. Hacker\",\n \"inbox\": \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"endpoints\": {\n \"multibox\": \"https://remote.example/multibox\"\n }\n}\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://remote.example/actors/af4c8205cd81\",\n \"type\": \"Person\",\n \"name\": \"Alice P. Hacker\",\n \"inbox\": \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"endpoints\": {\n \"https://w3id.org/fep/0499/multibox\": {\"id\": \"https://remote.example/multibox\"}\n }\n}\n
"},{"location":"fep/07d7/","title":"FEP-07d7: A Custom URL Scheme and Web-Based Protocol Handlers for Linking to ActivityPub Resources","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Jennifer Moore contact@jenniferplusplus.com POST /multibox HTTP/1.1\nHost: remote.example\nContent-Type: application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-activity\",\n \"target\": [\n \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"https://remote.example/inboxes/d21f509146e5\",\n \"https://remote.example/inboxes/68a7453f79e4\",\n \"https://remote.example/inboxes/655216a0be07\",\n \"https://remote.example/inboxes/84907eff485d\",\n ]\n}\nWITHDRAWN 2023-09-22 2025-04-22 #171 - codeberg
"},{"location":"fep/07d7/#summary","title":"Summary","text":"
mailto: and tel: are common examples from web standards. Zoom uses a customzoommtg: protocol to launch their desktop app, and Apple uses itms: to launch iTunes.web+activitypub: scheme and protocol. Typically either an ActivityPub server where the person has an account, or a client application they have installed on their device.web+activitypub: scheme. This scheme can be handled by web-based or native handlers registered with browsers by end-users. Because there's no guarantee that a given browser will have any registered handler for this scheme, these links SHOULD NOT be used in place of a link that refers to the resource by ID or an alternative HTML representation of it. Links using the web+activitypub: scheme SHOULD be used as an addition to those more canonical links.web+activitypub: scheme SHOULD be the same as the referenced ActivityPub object's ID. The address MAY instead be for an alternative human-readable address, or for an HTML representation of the object, as normal for the origin server. Applications using or generating these links should be aware that not all clients will be able to dereference those alternative or human readable addresses. The most interoperable choice is to use the object's canonical ActivityPub ID.web+activitypub:. That means the link MUST NOT simply prepend web+activitypub: onto a preexisting https: or other scheme, as this would violate the generic URI syntax specified by RFC-3986web+activitypub: link MAY encapsulate an intent for an activity to be performed on or with the linked object. For example, a pre-assembled link could be used to directly Follow an actor or to Announce an object. An encapsulated intent MUST be represented by appending a query string to the target URI. It's possible that the target URI already includes a query string. In this case, to encapsulate an intent, the intent MUST append a new query parameter to the existing query string. A web+activitypub: link MUST NOT encapsulate more than one intent.web+activitypub: link MUST NOT require that the intended activity is actually performed.intent=<activity> where <activity> is any of the following activities described by the Activity Streams vocabulary. The activity string SHOULD be all lowercase. It's permissible to use any casing. But, there are many systems in the wild which might intercept or preprocess the URIs in a web+activitypub: link, and not all of those will respect the original casing.
web+activitypub: link SHOULD NOT encapsulate an intent for any activity not listed above. In addition, the link MUST NOT encapsulate an intent for any of the following activities. Implementers should exercise discretion regarding activities added as extensions to the ActivityPub specification. You should err on the side taking no action where there is doubt about the safety or security of doing so.
web+activitypub: links MUST NOT include a username or password component. They also SHOULD NOT be relative URIs.web+activitypub: hyperlinks that reflect some expected uses.<a hfref=\"web+activitypub:shopping.example/pickup/12345?intent=arrive\">Check in to pick up your order</a><a href=\"web+activitypub:uss-enterprise.example/user/picard?intent=follow>Follow me on the fediverse</a><a href=\"web+activitypub:my-blog.example/article/write-your-first-fep>Read the article on your home instance</a>web+activitypub: URI scheme. Applications MAY use other available registration mechanisms, such as Android inents. The handler MUST attempt to load the specified URI as an application/ld+json document. If the origin does not respond with an ActivityPub object, the handler SHOULD fallback to other methods it uses to locate ActivityPub objects, such as webfinger.web+activitypub: link to target a relative URI, and there is some risk it could be used to facilitate scams or phishing attempts by making it appear some third party has access to data they shouldn't.web+activitypub: scheme, the handler SHOULD assume the origin uses HTTPS. The handler MAY attempt to use other schemes, such as did:. The handler MAY make this determination using any heuristic or algorithm the developers choose.web+activitypub: URI includes an encapsulated intent, the handler MAY prompt the user to perform that intended activity. The handler MUST NOT perform that activity without explicit confirmation from the user. The handler MUST NOT perform or offer to perform the intended activity if it is one of the ones listed below. These activities present an elevated risk of disrupting the user's social connections or enlisting them in malicious activities. The handler MAY perform other activities, but SHOULD do so with care. web+activitypub: links are usually created by third parties. Be mindful that there is some risk of exposing sensitive information or facilitating harmful behavior when third parties are allowed to direct other people's actions.
"},{"location":"fep/07d7/#33-servers","title":"3.3 Servers","text":"web+activitypub: handler. Servers SHOULD NOT do this unless the user has authenticated with the server. The server SHOULD also offer a similar option to de-register as a handler. Servers SHOULD NOT attempt to register as a handler unless initiated by the user in some way. Servers MUST NOT attempt to automatically register as a handler again after a user has declined to allow it.web+activitypub: links. They SHOULD use whatever mechanism is appropriate to the client to do so. For instance, Android apps would likely use the intent system, and Windows apps could set the appropriate registry flags. Clients SHOULD provide a mechanism to de-register as a handler, where possible. Clients MAY defer this capability to the operating system as appropriate.web+activitypub: link, creators SHOULD NOT include private or sensitive information in the link. Creators MUST NOT include any type of authenticating token or credential in the link. And handling applications themselves MUST apply all the normal and appropriate access controls when retrieving and displaying resources linked this way.
"},{"location":"fep/07d7/#copyright","title":"Copyright","text":"DRAFT implementation 2023-08-17 #147 Discussions codeberg"},{"location":"fep/0837/#summary","title":"Summary","text":"
"},{"location":"fep/0837/#proposals","title":"Proposals","text":"sequenceDiagram\n actor Alice\n actor Bob\n Note right of Alice: Alice publishes a Proposal\n Bob ->> Alice: Bob sends Offer(Agreement) activity\n Alice ->> Bob: Alice sends Accept(Agreement) activity\n Note over Alice, Bob: Alice and Bob complete the transaction\n Alice ->> Bob: Alice sends confirmation activity
id (REQUIRED): the proposal's unique global identifier.type (REQUIRED): the type of the object SHOULD be Proposal. If interoperability with other ActivityPub services is desirable, implementers MAY use object types from Activity Vocabulary, such as Note.purpose (REQUIRED): the type of proposal. Possible values are offer and request.attributedTo (REQUIRED): the identifier of an actor who published the proposal.name (RECOMMENDED): the title of the proposal.content (OPTIONAL): the description of the proposal. The type of content SHOULD be text/html.published (RECOMMENDED): the date and time at which the proposal was published.location (OPTIONAL): indicates a physical location associated with the proposal. The representation of location MUST conform to the recommendations of Activity Vocabulary document, section 5.3 Representing Places.publishes (REQUIRED): the primary intent of this proposal (see below).reciprocal (OPTIONAL): the reciprocal intent of this proposal (see below).unitBased (OPTIONAL): indicates whether the quantities expressed in the proposal can be multiplied or not. The default is false.to (REQUIRED): the audience of the proposal.
id (REQUIRED): the unique global identifier of the intent. Implementations SHOULD use URI fragments to identify intents associated with a given proposal. The RECOMMENDED fragment identifiers for primary and reciprocal intents are primary and reciprocal.type (REQUIRED): the type of the object MUST be Intent.action (REQUIRED): the type of economic transaction. The value of this property SHOULD be either deliverService or transfer.resourceConformsTo (RECOMMENDED): the type of an economic resource. Could be any URI.resourceQuantity (REQUIRED): the amount and unit of the economic resource. This is an object with two properties:hasUnit (REQUIRED): name of the unit, according to Ontology of units of Measure classification. The RECOMMENDED unit for countable items is one.hasNumericalValue (OPTIONAL): the amount of the resource. If not specified, arbitrary amounts can be used when responding to the proposal.availableQuantity (OPTIONAL): the quantity of the offered resource currently available.minimumQuantity (OPTIONAL): the minimum possible quantity of the resource.{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Proposal\": \"vf:Proposal\",\n \"Intent\": \"vf:Intent\",\n \"action\": \"vf:action\",\n \"purpose\": \"vf:purpose\",\n \"unitBased\": \"vf:unitBased\",\n \"publishes\": \"vf:publishes\",\n \"resourceQuantity\": \"vf:resourceQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\"\n }\n ],\n \"type\": \"Proposal\",\n \"id\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930\",\n \"purpose\": \"offer\",\n \"attributedTo\": \"https://market.example/users/alice\",\n \"publishes\": {\n \"type\": \"Intent\",\n \"id\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#primary\",\n \"action\": \"transfer\",\n \"resourceQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"1\"\n }\n },\n \"to\": \"https://www.w3.org/ns/activitystreams#Public\"\n}\n
"},{"location":"fep/0837/#publishing-a-proposal","title":"Publishing a proposal","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Proposal\": \"vf:Proposal\",\n \"Intent\": \"vf:Intent\",\n \"action\": \"vf:action\",\n \"purpose\": \"vf:purpose\",\n \"unitBased\": \"vf:unitBased\",\n \"publishes\": \"vf:publishes\",\n \"reciprocal\": \"vf:reciprocal\",\n \"resourceConformsTo\": \"vf:resourceConformsTo\",\n \"resourceQuantity\": \"vf:resourceQuantity\",\n \"availableQuantity\": \"vf:availableQuantity\",\n \"minimumQuantity\": \"vf:minimumQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\"\n }\n ],\n \"type\": \"Proposal\",\n \"id\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930\",\n \"purpose\": \"offer\",\n \"attributedTo\": \"https://market.example/users/alice\",\n \"name\": \"Offering used bike\",\n \"content\": \"Blue one-speed bike, 15 years old, some rust\",\n \"published\": \"2023-06-18T19:22:03.918737Z\",\n \"location\": {\n \"type\": \"Place\",\n \"longitude\": -71.0,\n \"latitude\": 25.0\n },\n \"publishes\": {\n \"type\": \"Intent\",\n \"id\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#primary\",\n \"action\": \"transfer\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q11442\",\n \"resourceQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"1\"\n },\n \"availableQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"1\"\n },\n \"minimumQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"1\"\n }\n },\n \"reciprocal\": {\n \"type\": \"Intent\",\n \"id\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#reciprocal\",\n \"action\": \"transfer\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q4917\",\n \"resourceQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"30\"\n }\n },\n \"unitBased\": false,\n \"to\": \"https://www.w3.org/ns/activitystreams#Public\"\n}\nCreate activity, or announced by group actors.href attribute MUST contain the proposal ID and its rel array MUST contain the string https://w3id.org/valueflows/ont/vf#Proposal. The value of mediaType attribute SHOULD be application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\".{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Person\",\n \"id\": \"https://market.example/users/alice\",\n \"inbox\": \"https://market.example/users/alice\",\n \"outbox\": \"https://market.example/users/alice\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Buy a bike\",\n \"mediaType\": \"application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"\",\n \"href\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930\",\n \"rel\": [\"payment\", \"https://w3id.org/valueflows/ont/vf#Proposal\"]\n }\n ]\n}\nNote objects.Agreement object wrapped in Offer activity to the actor indicated by the attributedTo property of the proposal. The proposing party MUST either commit to the action described in the proposal or send a rejection.Accept(Offer) activity back to the interested party.Reject(Offer) activity. The interested party MAY send Offer(Agreement) activities many times until agreement is reached.
id (OPTIONAL): the unique global identifier of the agreement. This property is REQUIRED for finalized agreements.type (REQUIRED): the type of the object MUST be Agreement.attributedTo (OPTIONAL): the identifier of an actor who created the agreement.stipulates (REQUIRED): the primary commitment associated with the agreement.stipulatesReciprocal (OPTIONAL): the reciprocal commitment associated with the agreement. It is REQUIRED if the corresponding proposal has a reciprocal intent.
id (OPTIONAL): the unique global identifier of the commitment. This property is REQUIRED for commitments in finalized agreements. Implementations SHOULD use URI fragments to identify commitments associated with a given agreement. The RECOMMENDED fragment identifiers for commitments satisfying primary and reciprocal intents of the proposal are primary and reciprocal.type (REQUIRED): the type of the object MUST be Commitment.satisfies (REQUIRED): the reference to an intent.resourceQuantity (REQUIRED): the amount and unit of the economic resource.unitBased property of the proposal is false, the amount of resources specified in commitments MUST be equal to amounts specified in the proposal. If the value is true, amounts MUST be multiples of amounts specified in the proposal. If unitBased property is not present on the proposal, arbitrary amounts can be used.Offer(Agreement) activity:
"},{"location":"fep/0837/#accepting-an-agreement","title":"Accepting an agreement","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Agreement\": \"vf:Agreement\",\n \"stipulates\": \"vf:stipulates\",\n \"stipulatesReciprocal\": \"vf:stipulatesReciprocal\",\n \"Commitment\": \"vf:Commitment\",\n \"satisfies\": \"vf:satisfies\",\n \"resourceQuantity\": \"vf:resourceQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\"\n }\n ],\n \"type\": \"Offer\",\n \"id\": \"https://social.example/objects/fc4af0d2-c3a1-409b-947c-3c5be29f49b0/offer\",\n \"actor\": \"https://social.example/users/bob\",\n \"object\": {\n \"type\": \"Agreement\",\n \"stipulates\": {\n \"type\": \"Commitment\",\n \"satisfies\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#primary\",\n \"resourceQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"1\"\n }\n },\n \"stipulatesReciprocal\": {\n \"type\": \"Commitment\",\n \"satisfies\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#reciprocal\",\n \"resourceQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"30\"\n }\n }\n },\n \"to\": \"https://market.example/users/alice\"\n}\nobject of Accept activity MUST be the id of the Offer activity previously sent to the actor.Accept activity MUST have the result property containing the Agreement object. The finalized agreement and corresponding commitments MUST have an id property. If a similar agreement between parties already exists, it MAY be updated and its id re-used. The finalized agreement MUST have an attributedTo property and its value MUST match the actor of the Accept activity.Agreement object from the Offer activity.url property containing one or more links to resources associated with the agreement. An example of such resource is a payment page (which can be represented as an FEP-0ea0 link).
"},{"location":"fep/0837/#rejecting-an-agreement","title":"Rejecting an agreement","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Agreement\": \"vf:Agreement\",\n \"stipulates\": \"vf:stipulates\",\n \"stipulatesReciprocal\": \"vf:stipulatesReciprocal\",\n \"Commitment\": \"vf:Commitment\",\n \"satisfies\": \"vf:satisfies\",\n \"resourceQuantity\": \"vf:resourceQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\"\n }\n ],\n \"type\": \"Accept\",\n \"id\": \"https://market.example/activities/059f08fa-31b1-4136-8d76-5987d705a0ab\",\n \"actor\": \"https://market.example/users/alice\",\n \"object\": \"https://social.example/objects/fc4af0d2-c3a1-409b-947c-3c5be29f49b0/offer\",\n \"result\": {\n \"type\": \"Agreement\",\n \"id\": \"https://market.example/agreements/edc374aa-e580-4a58-9404-f3e8bf8556b2\",\n \"attributedTo\": \"https://market.example/users/alice\",\n \"stipulates\": {\n \"id\": \"https://market.example/agreements/edc374aa-e580-4a58-9404-f3e8bf8556b2#primary\",\n \"type\": \"Commitment\",\n \"satisfies\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#primary\",\n \"resourceQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"1\"\n }\n },\n \"stipulatesReciprocal\": {\n \"id\": \"https://market.example/agreements/edc374aa-e580-4a58-9404-f3e8bf8556b2#reciprocal\",\n \"type\": \"Commitment\",\n \"satisfies\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#reciprocal\",\n \"resourceQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"30\"\n }\n },\n \"url\": {\n \"type\": \"Link\",\n \"href\": \"https://pay.example/invoices/7f1f0c81-0108-4c91-9cb1-d38ebccc3aa1\",\n \"rel\": \"payment\"\n }\n },\n \"to\": \"https://social.example/users/bob\"\n}\nobject of Reject activity MUST be the id of the Offer activity previously sent to the actor.content property indicating the reason for rejection.
"},{"location":"fep/0837/#confirmations","title":"Confirmations","text":"{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Reject\",\n \"id\": \"https://market.example/activities/8c05f97f-1531-4b70-9ca8-4ee4a09f36a4\",\n \"actor\": \"https://market.example/users/alice\",\n \"object\": \"https://social.example/objects/fc4af0d2-c3a1-409b-947c-3c5be29f49b0/offer\",\n \"content\": \"Not available\",\n \"to\": \"https://social.example/users/bob\"\n}\nAgreement object. The context property is RECOMMENDED for this purpose.
"},{"location":"fep/0837/#implementations","title":"Implementations","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Create\",\n \"id\": \"https://market.example/receipts/ad2f7ee1-6567-413e-a10b-72650cbdc743/create\",\n \"actor\": \"https://market.example/users/alice\",\n \"object\": {\n \"type\": \"Document\",\n \"id\": \"https://market.example/receipts/ad2f7ee1-6567-413e-a10b-72650cbdc743\",\n \"name\": \"Receipt\",\n \"context\": \"https://market.example/agreements/edc374aa-e580-4a58-9404-f3e8bf8556b2\",\n \"published\": \"2023-07-03T14:13:41.843794Z\"\n },\n \"to\": \"https://social.example/users/bob\"\n}\n
"},{"location":"fep/0837/#references","title":"References","text":"
"},{"location":"fep/0837/#copyright","title":"Copyright","text":"DRAFT implementation 2023-04-18 #88 Discussions codeberg"},{"location":"fep/0ea0/#summary","title":"Summary","text":"support property, which contains a text explaining how to support the content creator.
type (REQUIRED): the type MUST be Link.name (RECOMMENDED): the name property SHOULD contain a human-readable description of the payment link.href (REQUIRED): the href property MUST contain a payment URI. This can be a URL of a website, or any other kind of URI, such as 'payto' URI.rel (REQUIRED): the rel property MUST contain the string payment or an array containing that string. The payment relation type is defined in Link Relations Registry.attachment array of an actor or an object.{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Person\",\n \"id\": \"https://social.example/users/alice\",\n \"inbox\": \"https://social.example/users/alice/inbox\",\n \"outbox\": \"https://social.example/users/alice/outbox\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Donate\",\n \"href\": \"payto://iban/DE75512108001245126199\",\n \"rel\": \"payment\"\n }\n ]\n}\n
"},{"location":"fep/0ea0/#payment-links-as-actor-metadata","title":"Payment links as actor metadata","text":"{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Image\",\n \"id\": \"https://gallery.example/photos/123\",\n \"attributedTo\": \"https://gallery.example/users/alice\",\n \"name\": \"Painting of a cat\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Buy\",\n \"href\": \"https://gallery.example/photos/123/order\",\n \"rel\": [\n \"payment\",\n \"https://gallery.example/ns#buy\"\n ]\n }\n ]\n}\nname translates into field label and href translates into field value.
"},{"location":"fep/0ea0/#copyright","title":"Copyright","text":"DRAFT 2025-04-03 #546 Discussions codeberg"},{"location":"fep/1042/#summary","title":"Summary","text":"@username@domain syntax with Webfinger. link header pointing to the JSON-LD file for the AP object associated with the page. P2P sites should add extra links after this to the P2P URLs of the site which also use the rel=alternate attribute.Link element with the preferred url scheme and resume from there.links section of their JRD document. These links must use \"type\": \"application/activity+json\" and \"rel\": \"self\" along with their P2P URL under href. Make sure to place these P2P links after the HTTP versions to avoid bugs in implementations that check the first Link without looking at the URL scheme.@username@domain.com, clients should first resolve the Webfinger data using standard methods over HTTP. Clients should then loop through the links field and search for \"type\": \"application/activity+json\" and \"rel\": \"self\" objects with an href pointing to a P2P URL.id set to their P2P URL and Must link to the HTTP version of the data. Due to how most AP implementations resolve these links, make sure any HTTP links come first in your URL list so that they don't accidentally cause loading errors in implementations lacking support for P2P resolving.url field for objects with type: Link and rel: Alternate with the href property pointing to a URL with the supported P2P protocol.ipfs.io. From there use this domain as your root for all the URLs for HTTP versions of your data. Have your P2P objects link to the HTTP URLs and vise versa.{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", { \"@language\": \"en- CA\" }],\n \"type\": \"Person\",\n \"id\": \"https://staticpub.mauve.moe/about.html\",\n \"url\": [{\n \"type\": \"Link\",\n \"rel\": \"alternate\",\n \"mediaType\": \"application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"\",\n \"href\": \"ipns://staticpub.mauve.moe/about-ipns.jsonld\"\n }],\n \"outbox\": \"https://staticpub.mauve.moe/outbox.jsonld\",\n \"inbox\": \"https://social.distributed.press/v1/@mauve@staticpub.mauve.moe/inbox\",\n \"following\": \"https://staticpub.mauve.moe/following.jsonld\",\n \"followers\": \"https://staticpub.mauve.moe/followers.jsonld\",\n \"preferredUsername\": \"mauve\",\n \"name\": \"Mauve \ud83d\udc41\ufe0f\",\n \"summary\": \"This is an example static site profile. You can find the source code at https://github.com/RangerMauve/staticpub.mauve.moe/\",\n \"icon\": [{\n \"type\": \"Image\",\n \"mediaType\": \"image/png\",\n \"name\": \"Distributed Press logo\",\n \"url\": \"https://staticpub.mauve.moe/profile.png\"\n }]\n}\n{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", { \"@language\": \"en- CA\" }],\n \"type\": \"Person\",\n \"id\": \"ipns://staticpub.mauve.moe/about-ipns.jsonld\",\n \"url\": [{\n \"type\": \"Link\",\n \"rel\": \"alternate\",\n \"mediaType\": \"application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"\",\n \"href\": \"https://staticpub.mauve.moe/about.jsonld\"\n }],\n \"outbox\": \"ipns://staticpub.mauve.moe/outbox-ipns.jsonld\",\n \"inbox\": \"https://social.distributed.press/v1/@mauve@staticpub.mauve.moe/inbox\",\n \"following\": \"ipns://staticpub.mauve.moe/following-ipns.jsonld\",\n \"followers\": \"ipns://staticpub.mauve.moe/followers-ipns.jsonld\",\n \"preferredUsername\": \"mauve\",\n \"name\": \"Mauve \ud83d\udc41\ufe0f\",\n \"summary\": \"This is an example static site profile. You can find the source code at https://github.com/RangerMauve/staticpub.mauve.moe/\",\n \"icon\": [{\n \"type\": \"Image\",\n \"mediaType\": \"image/png\",\n \"name\": \"Distributed Press logo\",\n \"url\": \"ipns://staticpub.mauve.moe/profile.png\"\n }]\n}\n
"},{"location":"fep/1042/#references","title":"References","text":"{ \n \"subject\": \"acct:mauve@staticpub.mauve.moe\",\n \"aliases\": [\n ],\n \"links\": [\n {\n \"rel\": \"http://webfinger.net/rel/profile-page\",\n \"type\": \"text/html\",\n \"href\": \"https://staticpub.mauve.moe/about.html\"\n },\n {\n \"rel\": \"self\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://staticpub.mauve.moe/about.jsonld\"\n },\n {\n \"rel\": \"self\",\n \"type\": \"application/activity+json\",\n \"href\": \"ipns://staticpub.mauve.moe/about-ipns.jsonld\"\n }\n ]\n}\n
"},{"location":"fep/1042/#copyright","title":"Copyright","text":"DRAFT 2025-09-11 #675 Discussions codeberg"},{"location":"fep/11dd/#summary","title":"Summary","text":""},{"location":"fep/11dd/#what-is-a-context","title":"What is a context?","text":"context as a common property between a set of objects when they can be grouped together through reply association (e.g. a reply-tree).
"},{"location":"fep/11dd/#requirements","title":"Requirements","text":"context.attributedTo.
"},{"location":"fep/11dd/#example","title":"Example","text":"context MUST be resolvable.context MUST contain:
attributedTo denoting the authority/owner of that context.context.attributedTo MUST resolve to an actor (ActivityPub \u00a74.1 Actor Objects).context.attributedTo to a resolvable URI.
"},{"location":"fep/11dd/#context-inheritance","title":"Context Inheritance","text":"{\n \"id\": \"https://cats.example.org/1/context\",\n \"type\": \"OrderedCollection\",\n \"attributedTo\": \"https://cats.example.org/users/cat\",\n \"items\": [...]\n}\ninReplyTo) to another object.context from the root node, if the property is present and resolvable. Otherwise the object MUST NOT publish a context.context property outside the local domain, the context owner SHOULD be addressed (to, cc, audience).context outside the local domain, the object's membership MAY be verified by resolving the context directly.context's server may not reflect the object's membership in a timely manner due to delays in processing (i.e. network congestion, approval queues, etc.)
"},{"location":"fep/11dd/#copyright","title":"Copyright","text":"DRAFT 2024-12-08 #459 Discussions codeberg"},{"location":"fep/1311/#summary","title":"Summary","text":"mastodon.social yields that about half the posts contain an image attachment. The mechanism for these is poorly documented. For example, it is not mentioned in ActivityPub.{\n \"type\": \"Image\",\n \"name\": \"A beautiful cow\",\n \"url\": \"http://pasture-one-actor/assets/cow.jpg\",\n \"width\": 100,\n \"height\": 162,\n \"mediaType\": \"image/jpeg\",\n \"digestMultibase\": \"zQmaeDPzhNL32WQZnnzB1H6QJWvvFNEHdViDB71yrxyXU1t\",\n \"size\": 9045\n}\n{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/credentials/v2\",\n {\n \"size\": \"https://joinpeertube.org/ns#size\"\n }\n ],\n \"type\": \"Create\",\n \"actor\": \"http://pasture-one-actor/actor\",\n \"to\": [\n \"http://akkoma/users/witch\",\n \"https://www.w3.org/ns/activitystreams#Public\"\n ],\n \"id\": \"http://pasture-one-actor/actor/S5Szzuugy50\",\n \"published\": \"2024-12-05T08:18:48Z\",\n \"object\": {\n \"type\": \"Note\",\n \"attributedTo\": \"http://pasture-one-actor/actor\",\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\",\n \"http://akkoma/users/witch\"\n ],\n \"id\": \"http://pasture-one-actor/actor/qDqgbPpNQPw\",\n \"published\": \"2024-12-05T08:18:48Z\",\n \"content\": \"Recommended Image Format\",\n \"attachment\": [\n {\n \"type\": \"Image\",\n \"name\": \"A beautiful cow\",\n \"url\": \"http://pasture-one-actor/assets/cow.jpg\",\n \"width\": 100,\n \"height\": 162,\n \"mediaType\": \"image/jpeg\",\n \"digestMultibase\": \"zQmaeDPzhNL32WQZnnzB1H6QJWvvFNEHdViDB71yrxyXU1t\",\n \"size\": 9045\n }\n ]\n }\n}\nattachment of the Note object. Furthermore, in difference to the activity and the object, there is no id property nor actor or attributedTo property. This is on purpose, as those are inherited from the object the media attachment is attached to. See Content Licensing for discussion about not having an attributedTo property.id is useful to signify that it is not useful as an object without the note, it is attached to.attachment property, whose value is an array containing objects. Furthermore, the contained objects MUST have a type property. For it to be a media attachment the type property MUST be Audio, Image, or Video. However, the attachment property MAY contain other form of attachments, e.g. FEP-0ea0: Payment Links.{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"type\": \"object\",\n \"properties\": {\n \"attachment\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"examples\": [\"Audio\", \"Image\", \"Video\"]\n }\n },\n \"required\": [\"type\"]\n }\n }\n },\n \"required\": [\"attachment\"]\n}\ntype, a media attachment MUST also contain an url property providing the link to the media. The url property MAY also be an array, see Multiple Media Versions. However, this just to be future proof.name property providing an alternative plain text description of the media object.
"},{"location":"fep/1311/#current-state-of-type","title":"Current state of type","text":"{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"enum\": [\"Audio\", \"Image\", \"Video\"]\n },\n \"name\": {\n \"type\": \"string\",\n \"examples\": [\"A beautiful cow\"]\n },\n \"url\": {\n \"type\": [\"string\", \"array\"],\n \"examples\": [\"http://you.example/image.png\"]\n }\n },\n \"required\": [\"type\", \"url\"]\n}\nDocument for media attachments. This has the distinct disadvantage to using Audio, Image, or Video that the parser needs to inspect more content than type to discover what type of attachment it is.mediaType, discussed later.def is_media(attachment:dict) -> bool:\n media_type = attachment.get(\"mediaType\")\n if media_type is None:\n ... # handle error case\n\n main_type, _ = media_type.split(\"/\", 1)\n\n return main_type in [\"audio\", \"image\", \"video\"]\nurl to be an array, one needs a different check, so it turns into something like:def is_media(attachment:dict) -> bool:\n url = attachment.get(\"url\")\n if isinstance(url, list):\n return is_media(url[0])\n\n media_type = attachment.get(\"mediaType\")\n if media_type is None:\n ... # handle error case\n\n main_type, _ = media_type.split(\"/\", 1)\n\n return main_type in [\"audio\", \"image\", \"video\"]\nmediaType to the full object instead of the Link.Audio, Image, and Video for media attachments.GET request on the value of url or on href of the Link objects if url is an array. Just to mention it, this requirement might change, once Authentication and Authorization is dealt with.{\n \"type\": \"Image\",\n \"name\": \"A beautiful cow\",\n \"url\": \"http://pasture-one-actor/assets/cow.jpg\",\n \"width\": 100,\n \"height\": 162,\n \"mediaType\": \"image/jpeg\",\n \"digestMultibase\": \"zQmaeDPzhNL32WQZnnzB1H6QJWvvFNEHdViDB71yrxyXU1t\",\n \"size\": 9045\n}\nGET on http://pasture-one-actor/assets/cow.jpg and the result would bemediaType, digestMultibase, and size could be valid for any attached file, even a non media one, e.g. a text document. Let's quickly review them. mediaType is defined in the ActivityStreams Vocabulary. It describes the MIME type and tells us important information on how to render the file.digestMultibase is defined here as part of Verifiable Credential Data Integrity. The encoding of a digest in multibase with multihash is somewhat different to the rest of multicodec, because one first has a byte to indicate the format, then another one to indicate the length. This means in particular that all digestMultibase using sha-256 will start with zQm, the z indicating base58 encoding. Checking the digest is important to ensure integrity. As media is often hosted off site using S3, this seems important. For another usage see Content Addressed Storage.size being the file size in bytes is borrowed from PeerTube. The size should tell us if we want to preload the media or not.mediaType, digestMultibase, and size. The consumer of a media attachment SHOULD ensure integrity of the downloaded attachment based on digestMultibase, i.e. check the digest. The consumer of a media attachment SHOuLD decide based on size and mediaType the best way to consume the attachment.size and mediaType become more relevant when multiple versions of the media attachment are provided. For example, this could mean that in one feeds one only sees the low quality video by default.width and height. These properties are only relevant for an image and a video, but not for audio. Similarly, audio and video can have a duration, which images don't. Finally, Mastodon has introduced the additional properties
focalPointblurHashfocalPoint is user defined. There are a lot of other properties one can consider for media, e.g.
locationfps{\n \"type\": \"Video\",\n \"name\": \"A beautiful cow eating\",\n \"url\": [\n {\n \"type\": \"Link\",\n \"size\": 54373,\n \"digest\": \"zQmSzK5qEe5tpjwGMhmjx9RvVoPkWhEmCwxP2s7wPMpKMoK\",\n \"width\": 256,\n \"height\": 144,\n \"href\": \"http://pasture-one-actor/assets/cow_eating.mp4\",\n \"mediaType\": \"video/mp4\"\n },\n {\n \"type\": \"Link\",\n \"size\": 2271723,\n \"digest\": \"zQme2X4rgWuRdmAtGGMSEbdoeRQ2NAL2VptcdRGTYDZbSKG\",\n \"width\": 1920,\n \"height\": 1080,\n \"href\": \"http://pasture-one-actor/assets/cow_eating_hd.mp4\",\n \"mediaType\": \"video/mp4\"\n }\n ],\n \"duration\": \"PT3S\"\n}\nattributedTo property. Unfortunately, this has a lot of drawbacks. For example derekmuller is not an ActivityPub actor. Also attributing my cropped low resolution image to him, might be something he does not appreciate. Finally, just attributing this image is probably not enough, one should also inform people on how it is licensed.digestMultibase property, we can use this to index our media storage. This means that before downloading a file, we can check if we already have it.#8ACE00. Maybe you will also want to attach some lyrics. This means that your media content contains three parts of separate media type.
"},{"location":"fep/1311/#posts","title":"Posts","text":"
migration Collection","text":"Authors Status Type Date received Tracking issue Discussions Repository Jonny Saunders j@nny.fyi DRAFT implementation 2025-10-09 #702 Discussions codeberg"},{"location":"fep/1580/#summary","title":"Summary","text":"id, often hosted on a different server instance, however they do not describe a mechanism for moving objects that are owned4 by that actor.OrderedCollections created by the target instance:
migration collection that contains a mapping from source object URIs to new URIs on the target instance, andmoves collection that contains the actor Move activities that prove a migration has taken place and allows verification of object signatures in the case the source Actor is no longer available.Objects being migrated, supporting protocol evolution to unanticipated Object types across instances with varying support for them. Collection-based object migration is orthogonal to, and compatible with content-addressed or other portable object schemes (e.g. FEP-ef61).as:alsoKnownAs, activities and objects on a cooperative source instance continue to exist under the ownership of the now-inactive Actor, effectively becoming \"zombie objects\" that the new primary Actor has no direct control of; on an uncooperative or unavailable instance, these objects are lost even with a valid migration activity. Inability to migrate the objects owned by an Actor means that they are catastrophically lost in the case of instance shutdown, and has far-ranging impacts on the social and political structure of the fediverse by strongly dis-incentivizing movement between instances, and makes new user entry into the fediverse difficult and confusing, where a choice of instance made before having the chance to gain familiarity with the network becomes indefinite destiny.Move activities, one for each post, that all recipients need to keep up with would drown the fediverse. - The mapping from old to new URIs is not knowable in advance, as the local IDs used by one instance software need not map onto the IDs used by another, so a single batch update action is impossible. - There is no guarantee that a given Object on the source instance will be fully supported by the target instance, and some non-uri properties like proof signatures by definition must change after a migration, so 3rd-party instances must query the target instance to retrieve the updated versions of the migrated objects. - Objects are embedded within a larger graph that includes other collections like replies that must also be updated to reflect the migration. - Not all objects should be migrated, e.g. someone may want to make a \"fresh start\" on a new instance, keeping their followers/followed accounts, but only bringing some subset of objects (or none) along with them. - The source instance may not be cooperative or even online, so object migration may require the use of external backups which may be lossy or out of date. - Instance software that does not support object migration may be left in an inconsistent state with the rest of the fediverse that does.Collections for at-will consumption by 3rd-party instances. By storing a mapping between prior object URIs/IDs on the source instance and new URIs/IDs on the target instance, object migration can be done gradually, partially, safely, and selectively. This approach uses standard ActivityPub semantics and common collection enumeration operations, easing implementation.@context of affected Actor objects.
Move specifications in FEP-7628 and FEP-E965 where the primary actor of a set of linked identities is changed. Specifically, a \"irreversible\" migration of the movedTo variety, rather than the reversible, partial copiedTo. The \"Actor move\" activity is addressed to as:Public.Objects that are affected by the object migration.Offer[Move] activity emitted by the source instance addressed to the target instance's shared inbox (and not as:Public) after the source instance has validated the source and target actors are FEP-7628 linked actors in order to solicit an Accept[Move] from the target instance confirming the move. It is distinct from the \"actor move\" Move activity that is publicly emitted following acceptance of the move proposal.migration: https://w3id.org/fep/1580/ - the namespace created by this FEP (FEP-888d) - as: https://www.w3.org/ns/activitystreams - the ActivityStreams vocabulary - sec: https://w3id.org/security# - The Data Integrity vocabulary used by FEP-8b32 proofs.@context of any JSON-LD examples. Prefixes are included on first mention of a non-activitystreams term, but are otherwise omitted for concision.Move activity or its side effects as defined by FEP-7628, FEP-E965 and this FEP that inactivates the source account: i.e. the source account is either no longer available or has a movedTo property declared.Move activity is completed and verified, the target instance MUST perform an \"ingest routine\" that - enumerates all supported actor collections present in the actor object OR the set of objects approved during the moderation handshake, as described in Pre-Move Migration - ingests their contents, and - creates new Activities and Objects that preserve as much of their content as is supported.Block activities) MUST be ingested first so they may be used to control the visibility of objects within the migration collection. Migrated items MUST be subject to the same visibility constraints as on the source instance, including blocks, whenever possible.migration:migratedAt timestamp to display the time an object was migrated, and - a migration:migratedFrom URI to link to the prior object.migration Collection","text":"migration5 OrderedCollection that maps the object URIs from the source instance to their counterparts on the target instance. The migration collection SHOULD remain present on the actor object for the lifetime of the actor.migration collection MUST be ordered in reverse chronological order by the timestamp that the migrated counterpart of the original object is created on the target instance (NOT according to the date the original object was created or updated on the source instance).migration collection MUST contain a migration:moves URI reference to the accompanying moves collection (below).migration collection SHOULD use URI references in their object and target fields rather than inlining the materialized objects, and SHOULD paginate the collection as OrderedCollectionPages to reduce resources spent serializing and constructing the collection.migration collection MUST match the item visibility of the source objects, when possible. In particular: - Move activities for objects that are not addressed to as:Public MUST not be publicly displayed. - Move activities for non-public objects MAY be displayed when the request is authenticated as having been made by an Actor in the object's audience3. - Move activities MUST respect actor-level visibility controls like blocks.migration collection MAY be created incrementally, inserting migrated objects as they are ingested, or it may only populate the collection upon completion of the ingest routine.migration collection MUST still be published and its items field MUST be empty9.migration collection's migration:migrationComplete property must be set to true, indicating for consuming instances that for the given state of the moves collection (below) there will be no changes to the migration collection. To \"reopen\" the migration collection for consumption by third party instances, signaling new changes from e.g. an upload of an exported collection (below), the target instance MUST emit another (potentially duplicate) Move activity and set the migrationComplete property to false until the changes are completed. The target instance MAY delay setting migrationComplete to true in order to provide an interface to prompt the migrating actor to upload a previously exported collection of objects.{\n \"id\": \"https://example.com/chuckTargetson\",\n \"type\": \"Person\",\n \"...\": \"...\",\n \"migration\": \"https://example.com/chuckTargetson/migration\",\n \"moves\": \"https://example.com/chuckTargetson/moves\"\n}\nmigration OrderedCollection{\n \"id\": \"https://example.com/chuckTargetson/migration\",\n \"type\": \"OrderedCollection\",\n \"attributedTo\": \"https://example.com/chuckTargetson\",\n \"totalItems\": 100,\n \"first\": \"https://example.com/chuckTargetson/migration/page/0\",\n \"migrationComplete\": false,\n \"moves\": \"https://example.com/chuckTargetson/moves\"\n}\nmigration OrderedCollectionPage
"},{"location":"fep/1580/#moves-collection","title":"{\n \"id\": \"https://example.com/chuckTargetson/migration/page/0\",\n \"type\": \"OrderedCollectionPage\",\n \"partOf\": \"https://example.com/chuckTargetson/migration\",\n \"next\": \"https://example.com/chuckTargetson/migration/page/1\",\n \"items\": [\n {\n \"type\": \"Move\",\n \"actor\": \"https://source.example.com/barbaraSourceworth\",\n \"origin\": \"https://source.example.com/barbaraSourceworth/posts/12345\",\n \"target\": \"https://example.com/chuckTargetson/items/98765\"\n },\n {\n \"type\": \"Move\",\n \"actor\": \"https://source.example.com/barbaraSourceworth\",\n \"origin\": \"https://source.example.com/barbaraSourceworth/posts/abcde\",\n \"target\": \"https://example.com/chuckTargetson/items/zyxwp\"\n }\n {\"...\": \"...\"}\n ]\n}\nmoves Collection","text":"moves collection serves two purposes: - As a content-hashable indicator of the state of an account's migrations - As a durable source of authentication for confirming the validity of the items in the migration collection.moves collection contains the deduplicated Move activities emitted when an Actor is migrated. Move activities MUST be signed by the source actor using an FEP-8b32 proof. The move collection MUST make a copy of the source Actor object that contains the means of validating object integrity proofs available either inlined in the items of the Move collection or in the actors collection (described below). Source instances SHOULD inline the source Actor object in actor Move activities.Move activity, the target instance MUST create an inlined copy of the source Actor object in its actors collection. The actors collection is an OrderedCollection of inlined Actor objects sorted in lexicographic order according to their id (example below).moves collection MUST itself be signed with an FEP-8b32 proof. By including the inlined Actor objects and Move activities signed by the source Actor(s), and having the collection be signed by the target Actor, the moves collection serves as a self-contained, verifiable, bidirectional proof of the validity of the items in the migration collection.moves collection MAY include a full chain of account movement, including moves from prior instances to the source account where the target is not the current target account. Inclusion of account move activities MAY be used by consuming instances to migrate objects across several moves, but specifying chains of migration is out of scope for this FEP.moves collection with Actors inlined in Move activities{\n \"id\": \"https://example.com/chuckTargetson/moves\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n {\n \"id\": \"https://source.example.com/activities/12345\",\n \"type\": \"Move\",\n \"origin\": \"https://source.example.com/barbaraSourceworth\",\n \"target\": \"https://example.com/chuckTargetson\",\n \"actor\": {\n \"id\": \"https://source.example.com/barbaraSourceworth\",\n \"type\": \"Person\",\n \"...\": \"...\",\n \"publicKey\": {\n \"id\": \"https://source.example.com/barbaraSourceworth#main-key\",\n \"owner\": \"https://source.example.com/barbaraSourceworth\",\n \"publicKeyPem\": \"-----BEGIN PUBLIC KEY-----\\n...\"\n }\n },\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://source.example.com/barbaraSourceworth#main-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n }\n ],\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://example.com/chuckTargetson#main-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n}\nmoves collection with Actors inlined in the actors collection
"},{"location":"fep/1580/#migrations-from-exported-collections","title":"Migrations from Exported Collections","text":"{\n \"id\": \"https://example.com/chuckTargetson/moves\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n {\n \"id\": \"https://source.example.com/activities/12345\",\n \"type\": \"Move\",\n \"origin\": \"https://source.example.com/barbaraSourceworth\",\n \"target\": \"https://example.com/chuckTargetson\",\n \"actor\": \"https://source.example.com/barbaraSourceworth\",\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://source.example.com/barbaraSourceworth#main-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n }\n ],\n \"actors\": {\n \"id\": \"https://example.com/chuckTargetson/moves#actors\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n {\n \"id\": \"https://source.example.com/barbaraSourceworth\",\n \"type\": \"Person\",\n \"...\": \"...\",\n \"publicKey\": {\n \"id\": \"https://source.example.com/barbaraSourceworth#main-key\",\n \"owner\": \"https://source.example.com/barbaraSourceworth\",\n \"publicKeyPem\": \"-----BEGIN PUBLIC KEY-----\\n...\"\n }\n }\n ]\n },\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://example.com/chuckTargetson#main-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n}\nMove activity was emitted, supporting instances SHOULD provide a means of uploading and ingesting exported collections, and if they do, they SHOULD ingest and otherwise handle objects from an export format of a collection identically as those that were retrieved from the source instance, ignoring any duplicates. Move activity was emitted by the source instance - the case where an account Move activity was not emitted and the source instance is unavailable or hostile is outside the scope of this FEP. However, if the actor export contains the private key that accompanies the public key used for object proofs, and a Move activity can be created and validated independently of the source instance, the actor Move activity SHOULD be considered equivalent to one emitted by the source instance (FEP-7628 \"Pull\" mode migrations). Supporting software MAY provide interfaces and mechanisms for import and export of private actor keys and creation of Move activities from those exports.blocks for Block activities, though the specific terms and Collection structure are left unspecified here7.migration collection from the target instance and store a mapping from the source to target objects. - present the migrated objects with a FEP-7628 movedTo property containing the new target object URI while the object remains present - if the source object is removed, return a 301 redirect to the target object URI.moves and migration on the target instance.Move activity, 3rd-party instances MUST initiate a \"migration routine\" (below) that consumes the migration collection of the target Actor. Move activity was not observed by a 3rd-party instance (e.g. because the instance was offline or unreachable), a 3rd-party instance MUST initiate the \"migration routine\" upon observing either: - a FEP-7628 movedTo property in the source Actor object AND reciprocal alsoKnownAs property in the target Actor, or - the presence of an unprocessed Move action in the target Actor's moves collection.migration collection is not marked as complete (via migrationComplete == true), 3rd-party instances MUST periodically check for new items, with some long timeout in case the target instance is malfunctioning or incorrectly implements migrationComplete. Move activities, but are NOT REQUIRED to re-validate each of the migrated items. Move activity, 3rd-party instance observes a mismatch in migration data (e.g. mapping to different URIs, differences in content) between the source and target instances, it MUST prefer the target instance's data, as the Move activity has designated the target Actor and target instance as the primary Actor in custody of the objects.Move activity, 3rd-party instances MUST update all references to the source Actor in its internal representations of objects to refer to the target Actor - e.g. in \"mentions\" or as:Links. They MAY preserve representations of both the source and target actor for the purposes of representing and displaying the Move.migration collection that the 3rd-party instance has a local representation of, 3rd-party instances MUST - Update the item's ID/URI from the origin to the target. - Request any updated fields in the target object from the target instance. - Update its internal object representations so it is indistinguishable from the case where source object was created by the target actor and instance (except for migration-specific fields like migratedFrom).Update or other activities while updating migrated objects.migration mapping that they do not already have a local representation of: object migration is not backfilling. migration collection, which is effectively a mapping from source to target URIs, the instance should update the object's URI without waiting for a full refresh of the object. The target instance MAY update the content of the objects during migration, but 3rd-party instances SHOULD continue to treat them as unchanged until told otherwise by the target instance. migratedFrom and other information needed to reconcile activities and objects from other 3rd-party instances that do not implement object migration: e.g. if actor xena migrated from instance a to instance b, and instance c has a receives an activity from instance d that is inReplyTo object zzz from @xena@a, but instance c knows @xena@a is now @xerxes@b and zzz is now yyy, it should transform the inReplyTo field while processing that activity.migration pages and refreshes of migrated objects should be given ample inter-request delays. The target instance is responsible for communicating rate limits, but 3rd-party instances should also be polite by default and aim to complete, for example, a migration of an Actor with 10,000 objects on the order of ~days to a week.target for activities in the object's Like collection to refer to the new URI), it should be made, but it is the responsibility of the target instance to include any objects that should be migrated in the migration collection.Updates that contain only the changed fields for migrated objects, but specification of such a change collection is outside the scope of this FEP.movedTo property is present on an object), the 3rd-party instance must verify the existence of a relevant actor Move activity, and if found, perform the migration routine on that object.
"},{"location":"fep/1580/#pre-move-moderation","title":"Pre-Move Moderation","text":"migration collections, and when present they should create redirects and populated movedTo fields accordingly.migration and moves collections accordingly. In the initial window of implementation, there will be many previously-migrated accounts that no longer have access to a signed account Move activity to prove a given set of entries in the migration collection. However they should still publish those collections however possible, and allow 3rd-party instances to decide how to handle previously-accepted account Moves. This transition path will be the subject of further expansion and revision as lessons are learned during implementation. In cases where the source instance is no longer available and the moved Actor has an export copy of their prior posts, this FEP should support recovering those posts to the target instance.migration collections encountered while refreshing Actors is a formally spec'd initiation route for migration, so no special consideration should be needed. The gradual deployment of object migration across instances should soften some of the resource burden of needing to migrate a decade of objects.Move having already been emitted and accepted is a precondition of this spec. This section8 specifies behaviors before the actor Move activity has been emitted and accepted.Move activity, source and target instances MUST complete the following Proposed Move handshake:
verification: The source instance verifies the source and target actors are Linked Actors (as per FEP-7628)subsetting: OPTIONAL - The source instance allows the source actor to subset the objects they wish to migratesyn: The source instance sends a Offer[Move] \"Proposed Move\" activity addressed to the target instance's shared inbox.moderation: OPTIONAL - The target instance may perform a \"moderation routine\" to remove or update objects within the set of objects to be migratedsyn-ack:Accept[Move] activity addressed to the source actor indicating it accepts the Proposed Move with no changes.TentativeAccept[Move] activity addressed to the source actor with an attached collection of Remove and Update activities.Reject[Move] activity addressed to the source actor, and the source actor is notified that their request to move was rejected.review: If a TentativeAccept[Move] response was received, the source instance provides a means of the source actor reviewing the changes to the migrated items required by moderation and indicating whether they accept them or wish to cancel the actor move operation.ack:Accept[Move] response was received, the public Actor Move activity is emitted.TentativeAccept[Move] was received and the source Actor indicated that they accept the changes required by moderation in the review phase, the public Actor Move activity is emittedTentativeAccept[Move] was received and the source actor indicated that did not accept the changes from moderation and wish to cancel the actor move, a Reject[TentativeAccept[Move]] activity is emitted addressed to the target instance's shared inbox.subsetting: The source instance MAY provide the source actor some means of selecting which objects they choose to migrate. If such a subsetting feature is provided, the resulting subset of objects to migrate MUST be included as an attachment to the Proposed Move. If a collection of objects to migrate is attached to a proposed move, the target instance MUST consider only these objects in the subsequent moderation stage and replace the set of objects that would otherwise be discovered by enumerating the source actor's collections in the ingest routine. An empty attachment collection MUST be interpreted as the intent to migrate only the actor with none of its objects. If an attachment of objects to migrate is absent, the target instance MUST consider the set of objects to migrate as the set of importable objects in the source actor's collections. moderation: The target instance MAY provide some means of moderating the collection of objects to be migrated. The target instance MUST return a TentativeAccept[Move] activity with an attached collection of Update[Object] and Reject[Object] activities that represents the objects that would be changed or removed in a migration, relative to the collection of proposed objects to migrate. Following the ActivityPub specification of server-to-server Update actions, the complete object with modifications made must be included in Update[Object] responses, rather than a set of partial updates (only those fields that have changed). To avoid specious updates that are trivially implied by an Actor Move (e.g. the proof field changing) or would require the target instance to have already imported the object (e.g. the id field changing), the updated object MUST NOT contain changes to fields that can only be determined after an object is migrated. The source instance MUST NOT validate the updated objects against the proof field - as they will by definition fail the integrity proof.Move and TentativeAccept[Move] activities MUST be treated as if the contained objects were included in the attachments directly - proposing to migrate or moderate a Collection means that one is proposing to migrate or moderating all the items in that Collection.migration collection as they are approved. The target instance MUST immediately notify the target actor of the pending moderation and advise them to make a backup of the objects from the source instance to preserve any objects that may be removed or modified by moderation.Move request, e.g. if it does not support this FEP, the source instance MUST notify the source actor that their objects may not be migrated and allow them to approve or cancel emission of the public actor Move activity.{\n \"id\": \"https://source.example.com/activities/23456\",\n \"type\": \"Move\",\n \"to\": \"https://example.com/sharedInbox\",\n \"origin\": \"https://source.example.com/barbaraSourceworth\",\n \"target\": \"https://example.com/chuckTargetson\",\n \"attachment\": {\n \"type\": \"Collection\",\n \"items\": [\n \"https://source.example.com/barbaraSourceworth/posts/111\",\n \"https://source.example.com/barbaraSourceworth/posts/222\",\n \"https://source.example.com/barbaraSourceworth/followers\"\n \"...\"\n ]\n } \n} \n
Proposed Move specifying that no objects should be migrated with an empty collection
{\n \"id\": \"https://source.example.com/activities/34567\",\n \"type\": \"Move\",\n \"to\": \"https://example.com/sharedInbox\",\n \"origin\": \"https://source.example.com/barbaraSourceworth\",\n \"target\": \"https://example.com/chuckTargetson\",\n \"attachment\": {\n \"type\": \"Collection\",\n \"items\": []\n } \n} \n TentativeAccept[Move] specifying a set of removals and updates from moderation
The original proposed collection of objects is modified such that - post 111 would not be migrated - post 222 would have its attachment removed (but otherwise remain unchanged, assuming the \"...\" expands to the original object's remaining fields)
{\n \"id\": \"https://example.com/activities/45678\",\n \"to\": \"https://source.example.com/barbaraSourceworth/inbox\",\n \"type\": \"TentativeAccept\",\n \"object\": \"https://source.example.com/activities/23456\",\n \"attachment\": {\n \"type\": \"Collection\",\n \"items\": [\n {\n \"type\": \"Reject\",\n \"object\": \"https://source.example.com/barbaraSourceworth/posts/111\"\n },\n {\n \"type\": \"Update\",\n \"object\": {\n \"id\": \"https://source.example.com/barbaraSourceworth/posts/222\",\n \"attachment\": null,\n \"content\": \"The thing I originally posted, since server-to-server Updates aren't partial updates\",\n \"...\": \"...\"\n }\n }\n ]\n } \n} \n"},{"location":"fep/1580/#discussion","title":"Discussion","text":"(This section is non-normative)
Feedback and edits are welcome for each of these sections to include the inevitable caveats and consequences unforeseen by the author.
"},{"location":"fep/1580/#out-of-scope","title":"Out of Scope","text":"This FEP is focused exclusively on the circumstance where a valid Move activity exists or can be created, and thus does not consider
Move activity or export a private key to sign one created by the target instance.id schemes typically don't require object migration (indeed that is their primary purpose), and thus this FEP does not apply to them. Implementing object migration for HTTP/URL objects should not be construed as competing with or trading off with instance-independent objects or alternative id or resolution schemes, and is intended to resolve a need in existing systems rather than preventing new systems from being created.copiedTo-style, non-final redirects) are not considered in this FEP, and it assumes that there is a single target instance and actor which becomes the primary Actor in custody of the source actor's objects. It is not possible in this formulation for the source Actor to indicate which objects might be owned by different targets or to resolve conflicts over object ownership between multiple target actors, though future amendments could allow the actor Move activity to contain scoping information that could accomplish that. Merging actors (multiple source actors moving to a single target actor) is explicitly supported by the moves collection, however.Migrating objects from one instance to another involves changing their moderation context: the source and target instances are likely to have differences in rules governing content, behavior, and connections between actors. If migrating objects is subject to different rate limits and moderation control than normal object creation, it may serve as an abuse vector, where content that is not allowed on the target instance can be created at faster rates or out of the eye of moderators1. As a result, target instances may want some means of moderating incoming content.
The potential implementations of moderation for migrated content might vary considerably, and this FEP is not intended to overly constrain them in advance. However since the needs of the migrating actor and the needs of the instance may conflict during moderation, some guidance is provided above (Pre-Move Moderation) to balance them.
Some examples of conflicting needs between involved parties: - The migrating actor has an interest in maintaining whatever subset of their previously-created objects they elect to migrate, but the target instance has an interest in maintaining the norms and rules that govern instance content. There is a risk of permanent loss if the target instance chooses to not ingest some objects during a migration and the source instance chooses to remove them following a migration. - Reviewing a large number of objects might create a heavy moderation burden on target instances, but a migrating actor should not be left in a prolonged state of uncertainty after their account has already been moved regarding whether their content will be allowed to migrate with them. - The migrating actor may wish to revoke their move to a target instance after seeing the results of moderation, but repeated moves create a large amount of traffic on the network, and the target instance should not have to serve as an intermediate host to content it views as against its rules in order to facilitate a second-order move. - Current implementations of FEP-7628 account movements may not be subject to moderation, as they only take into account the actor, not any of its objects. An overly-complex moderation spec in this FEP may prolong its implementation, prolonging the broader harms from the absence of object migration.
The above spec language reflects a strategy for balancing these conflicting needs: - Moderation should take place between the proposal of the Move and its acceptance - Target instances should make modifications from moderation to the collection of migrated objects transparent to the migrating actor. - Actors must have some means of changing their mind about a move if the terms of moderation are unacceptable to them after the target instance reviews any imported content. - If subsetting and moderation are both waived, or the source or target instances do not implement this FEP, the handshake can fallback to behavior equivalent to emitting a public actor move activity without the handshake. This can allow for staged implementation, handling the core migration routines first, and the frontend work needed for moderation afterwards.
The pre-move moderation spec is intended to be very general to accommodate different strategies for managing bulk moderation. For (non-exhaustive) example:
Update for all objects with attached media with that attachment removed, e.g. if storage costs would be prohibitive or an instance doesn't support the content type.Remove for all objects not addressed to as:Public or the \"followers\" collection.The above specification regarding pre-move moderation is compatible with FEP-e965, as it only requires that actors be publicly declared as linked in order for a Move to be considered valid - this spec adds additional optional behavior prior to the Move activity being broadcast to 3rd-party instances without modifying that requirement.
Migrating tens of thousands of objects across hundreds or thousands of instances is an intrinsically costly operation. The strategy here attempts to mitigate extreme resource requirements by allowing migrations to happen gradually and partially. Rather than emitting a flood of thousands of activities, 3rd-party instances can accomplish a majority of the important changes needed by consuming a collection of small Move activities that contain pairs of URIs, and then slowly backfill remaining migration changes over days or weeks. The need for object migration is so high that the resource cost is well worth it, and after the initial period after deployment where all the migrations until the present need to be processed, account migrations are relatively rare (i.e. Mastodon has a 30-day cooldown).
Instances that support object migration should remain interoperable with instances that do not, and that should be achievable by keeping a map from prior to current object IDs and transforming references to migrated objects when processing activities from non-migratory instances. Some inconsistency in network state is inevitable when implementing new behaviors, but this FEP shouldn't introduce new baffling complexities to network state: the current behavior of objects on accounts that have migrated and are no longer active is that they are inert - so additional replies can be created but they won't be received, and so on. Any change that introduces even partial object migration is a net-positive for network coherence.
"},{"location":"fep/1580/#security","title":"Security","text":"The heavy-lifting of secure object migrations is already handled by the existing account Move specs, which establish a trusted means of declaring that one actor is now another actor. This FEP should not introduce new risks for account takeover that are not already inherent in the existing account Move specs. Account Move activities must always be validated by an integrity proof by the source actor, accordingly attempts at account hijacking or spurious attribution must be rejected. However, since there is no mechanism for forcing key expiration or rotation, and thus a compromised secret key may be used to initiate a hostile account migration using an actor export, the Migrations from Exported Collections section is left as an optional recommendation subject to more formal specification in the future.
Importing an external backup introduces the possibility of object forgery. Assuming a valid account Move and proper signing and validation, this is actually not a problem: uploading an export where the author edits all their posts or adds new ones is effectively the same as them batch-posting a bunch of back-dated posts. This FEP does not provide for modifying objects which the target actor does not own, and the target actor may do whatever they please with their objects. 3rd-party instances should be responsible for notifying 3rd-party accounts of changes to objects that they have interacted with, as is done with updates.
Ingesting objects from collections hosted by a hostile source instance poses a larger problem: implementations may want to consider some interface that displays changes made to objects during collection ingestion and allows the target actor to reject them. Uploading some prior export of objects should override the versions of objects served by the source instance, as those should reflect the will of the actor more directly. This is another implementation detail that will require expansion and revision from experiences during implementation.
The author notes they are not a security expert and this FEP and all implementations should be independently audited.
"},{"location":"fep/1580/#privacy","title":"Privacy","text":"No new access routes should need to be established, as all operations use existing Collection mechanisms which must support filtering for object access already. the migration collection should not be an information leak (e.g. by indicating that some private objects exist by referring to their id, which may have more information beyond mere existence, as is the case with the snowflake id scheme used by Mastodon) and only contain information that is already publicly enumerable via public collections on the source actor.
fep-1580.jsonld - The JSON-LD context document to be included in the @context statement of actors on implementing instances.migration.yaml - A linkml schema that provides a concrete form of the ActivityPub/ActivityStreams extensions described in the text. migration.owl.ttl - An OWL schema generated from the LinkML schemaThank you to those that helped provide feedback and recommendations for this FEP:
(in alphabetical order by handle, please feel free to make a PR to add yourself if you helped!) - ansuz - Advice from cryptography POV - apropos - Clarifying questions - silverpill - Suggestions re: clarity and use of ActivityStreams - trwnh - Help with JSON-LD, critical feedback.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/1580/#footnotes","title":"Footnotes","text":"The problem of abusive content being created on an instance is not unique to migrations, and most software implementing ActivityPub has means of creating abusive content rapidly (through API requests and automation) and outside the normal visibility of moderation (with direct messages or other scoped visibility settings). While the problem is not unique, this section attempts to address some new potentials for abuse vectors that may exist given prior implementations of actor moves that did not consider migrating objects along with the actor.\u00a0\u21a9
The description of alsoKnownAs cautions about treatment as an equivalence relationship, however the combination of a movedTo from the source Actor and an inverse alsoKnownAs property from the target Actor does indicate an equivalence relationship where the source Actor is inactive and has become the target Actor. copiedTo does not necessarily indicate an equivalence, as according to the DID spec, linked Actors that are still in use can be used for different purposes. Accordingly we do not consider copiedTo Actor redirects in this FEP.\u00a0\u21a9
Members of as:audience, as:to, as:bto, as:cc, as:bcc, and any other properties used for scoping visibility or delivery.\u00a0\u21a9
\"Ownership\" in this document is defined according to FEP-fe34: Ownership \u21a9\u21a9
the CURIE for the collection is, maybe unhelpfully, migration:migration to refer to https://w3id.org/fep/1580/migration \u21a9
\"Push\" mode migration, as defined in FEP-7628 \u21a9\u21a9
This is possible since the target instance must enumerate all collections linked from an actor document if a specific collection of objects to migrate is not provided by the Offer[Move] handshake.\u00a0\u21a9
Until the next heading.\u00a0\u21a9
The inclusion of an empty migration collection is to differentiate \"a migration that migrated no objects\" from \"an instance that doesn't implement this FEP,\" which would be ambiguous if migration was omitted.\u00a0\u21a9
DRAFT implementation 2024-11-23 #449 Discussions codeberg"},{"location":"fep/171b/#summary","title":"Summary","text":"This document specifies a model for managing conversations in ActivityPub network. It is based on the implementation of Conversation Containers in Streams.
In this model conversations are represented as collections controlled by a single actor. Such conversations take place within a specific audience and may be moderated.
"},{"location":"fep/171b/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/171b/#containers","title":"Containers","text":"A conversation container is implemented as a collection. Every item in that collection is an Add activity where object is another activity (such as Create, Update, Delete, Like, Dislike, EmojiReact or Announce). The conversation owner distributes Add activities to other participants, thus keeping their views of the conversation synchronized.
sequenceDiagram\n actor Alice\n actor Bob\n actor Charlie\n Note right of Alice: Alice starts a conversation by creating a post\n Alice ->> Bob: Add(Create(Note))\n Alice ->> Charlie: Add(Create(Note))\n Note left of Bob: Bob reacts to the post\n Bob ->> Alice: Like(Note)\n Note right of Alice: Alice receives activity and distributes it to other participants\n Alice ->> Charlie: Add(Like(Note))"},{"location":"fep/171b/#container-collection","title":"Container collection","text":"OrderedCollection.attributedTo property containing the id of the conversation owner.collectionOf property with value Activity.The audience of a conversation SHOULD be able to access the container collection. If a server forwards activities without storing them, it is permitted to not publish the collection. However, that would limit the ability of other servers to backfill conversations.
Actors who are not part of the audience MUST be denied access to the collection.
Note
The required collection type might be changed to a more descriptive one (such as ConversationContainer) in a future version of this document. That will allow identification of Add activities belonging to a conversation container by the value of Add.target.type.
Note
In addition to the conversation container collection, implementers MAY provide collection that represents the conversation tree and contains posts.
"},{"location":"fep/171b/#adding-activities-to-a-conversation","title":"Adding activities to a conversation","text":"Conversation owner can add any activity to the conversation. However, if a context property is present on the activity, its value SHOULD be identical to the ID of a conversation container.
When activity is added to the conversation, its owner sends an Add activity to the conversation audience (usually defined by a top-level post).
Add activities published by the conversation owner MUST have a target property containing a partial object:
type: OrderedCollectionid: the id of the conversation container.attributedTo: the id of the collection owner.Add activities published by the conversation owner MUST be added to the conversation container collection.
Note
The \"conversation outbox\" model where the target of Add activity is a collection of Add activities is not compatible with ActivityStreams definition of Add activity, according to which Add activity indicates that the actor has added the object to the target.
The author of a top-level post it not necessarily the conversation owner. When owner is a group, conversations can be started by any of its members.
The top-level post MUST have a contextHistory property that refers to the conversation container. The presence of this property indicates that publisher implements conversation containers.
Note
In addtion to contextHistory property, implementers MAY add a context property that refers to a collection of posts.
Regardless of their audience, all activities in a conversation SHOULD only be delivered to the conversation owner.
Conversation participants SHOULD reject conversation activities that have not been added to the conversation by its owner.
The audience of a reply MUST be copied from a conversation root.
Reply with a different audience can be created by starting a new conversation and including a FEP-e232 link to the post instead of specifying it in inReplyTo.
When conversation owner does not want to add activity to a conversation, that activity is ignored and a corresponding Add activity is not published.
To remove a previously approved post from a conversation, its owner publishes a Delete activity where object is the post that must be removed. This activity is then wrapped in Add activity and distributed to the conversation audience.
Note
Actor shouldn't be able to delete objects it didn't create. In a future version of this document Delete might be replaced with Remove(target: Thread).
Conversation participants can retrieve missing activities by reading the conversation container collection.
"},{"location":"fep/171b/#authentication","title":"Authentication","text":"When an ActivityPub server receives an Add activity in its inbox, it MUST perform the authentication procedure according to FEP-fe34:
Add.object is not embedded, fetch it. If location of the fetched activity has the same origin as Add.object, add it to the conversation.Add.object is embedded, check whether Add.actor and Add.object.actor have the same origin.Add.object to the conversation.Add.object to the conversation.Add.object by its id. If location of the fetched activity has the same origin as Add.object.id, add fetched activity to the conversation.The processing of unauthenticated embedded activities is strongly discouraged. If such activities are not rejected by the consumer, a malicious conversation owner may be able to perform a cache poisoning attack and overwrite any actor or a post in consumer's local cache by sending a forged Update(Actor) or Update(Object) wrapped in an Add activity.
Warning
Sometimes activities have non-dereferenceable identifiers. That may prevent their authentication.
"},{"location":"fep/171b/#examples","title":"Examples","text":"Example of an Add activity for a reply to a followers-only post:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Add\",\n \"id\": \"https://alice.example/activities/add/1\",\n \"actor\": \"https://alice.example/actors/1\",\n \"object\": {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Create\",\n \"id\": \"https://bob.example/activities/create/1\",\n \"actor\": \"https://bob.example/actors/1\",\n \"context\": \"https://alice.example/contexts/1\",\n \"contextHistory\": \"https://alice.example/contexts/1/history\",\n \"object\": {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Note\",\n \"id\": \"https://bob.example/posts/1\",\n \"inReplyTo\": \"https://alice.example/posts/1\",\n \"content\": \"This is a reply\",\n \"to\": [\n \"https://alice.example/actors/1\",\n \"https://alice.example/actors/1/followers\"\n ]\n },\n \"to\": [\n \"https://alice.example/actors/1\",\n \"https://alice.example/actors/1/followers\"\n ]\n },\n \"target\": {\n \"type\": \"OrderedCollection\",\n \"id\": \"https://alice.example/contexts/1/history\",\n \"attributedTo\": \"https://alice.example/actors/1\"\n },\n \"to\": [\n \"https://bob.example/actors/1\",\n \"https://alice.example/actors/1/followers\"\n ]\n}\n Example of a container of a followers-only conversation:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/171b\"\n ],\n \"type\": \"OrderedCollection\",\n \"id\": \"https://alice.example/contexts/1/history\",\n \"attributedTo\": \"https://alice.example/actors/1\",\n \"collectionOf\": \"Activity\",\n \"orderedItems\": [\n \"https://alice.example/activities/add/1\"\n ]\n}\n"},{"location":"fep/171b/#comparison-with-other-proposals","title":"Comparison with other proposals","text":"object of Add is an object, not an activity, and conversation collection contains added objects. Reject(Create) activity is generated for rejected posts. Conversation participants are expected to add a target property to posts.Announce activity is used instead of Add. Conversation and related activities are synchronized between participants, but conversation backfilling mechanism is not specified.Accept or Reject activity is generated for every interaction (in a conversation container Add activity is generated when activity is approved, and rejected activities are ignored). Conversation is not synchronized between participants, but can be backfilled by recursively fetching replies collections.CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/1970/","title":"FEP-1970: Chat Links","text":"Authors Status Date received Tracking issue Discussions Repository John Livingston git@john-livingston.frDRAFT 2023-07-04 #128 - codeberg"},{"location":"fep/1970/#summary","title":"Summary","text":"This FEP describes a way to attach a chat room to ActivityPub actors and objects. The chat room itself can be a web page, a XMPP room, a Matrix room, an IRC channel, ... The chat itself does not necessarily publish messages using ActivityPub.
"},{"location":"fep/1970/#chat-links","title":"Chat links","text":"Chat link is an object with the following properties:
type (REQUIRED): the type MUST be Link.name (RECOMMENDED): the name property SHOULD contain a human-readable description of the chat link.href (REQUIRED): the href property MUST contain a chat URI. This can be an URL of a website, or any other kind of URI, such as a XMPP uri.rel (REQUIRED): the rel property MUST contain the string discussion or an array containing that string. The discussion relation type is proposed as a HTML5 link type extensions.Chat links MUST be added to the attachment array of an actor or an object.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.tld/video/123\",\n \"attributedTo\": \"https://example.tld/users/alice\",\n \"name\": \"Let's go live!\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n }\n ]\n}\n"},{"location":"fep/1970/#basic-actor-attachment","title":"Basic actor attachment","text":"{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Person\",\n \"id\": \"https://example.tld/users/alice\",\n \"inbox\": \"https://example.tld/users/alice/inbox\",\n \"outbox\": \"https://example.tld/users/alice/outbox\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n }\n ]\n}\n"},{"location":"fep/1970/#multiple-way-to-join-the-chat","title":"Multiple way to join the chat","text":"In the following example, the chat room can be joined using a web browser or a xmpp client:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Video\",\n \"id\": \"https://example.tld/video/123\",\n \"attributedTo\": \"https://example.tld/users/alice\",\n \"name\": \"Let's go live!\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n },\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"xmpp://123@room.example.tld?join\",\n \"rel\": \"discussion\"\n }\n ]\n}\n"},{"location":"fep/1970/#live-video-stream","title":"Live video stream","text":"For a live stream, represented by a Video object, the chat link could link to the room where viewers can interract with the streamer.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Video\",\n \"id\": \"https://example.tld/video/123\",\n \"attributedTo\": \"https://example.tld/users/alice\",\n \"name\": \"Let's go live!\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n }\n ]\n}\n If the chat room is unique per streamer (and common to all the streamer videos), it can be attached to both the Video and the Person object.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Person\",\n \"id\": \"https://example.tld/users/alice\",\n \"following\": \"https://example.tld/users/alice/following\",\n \"followers\": \"https://example.tld/users/alice/followers\",\n \"name\": \"Alice\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n }\n ]\n}\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Video\",\n \"id\": \"https://example.tld/video/123\",\n \"attributedTo\": \"https://example.tld/users/alice\",\n \"name\": \"Let's go live!\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n }\n ]\n}\n If the chat room is unique to a streamer's channel, it can be linked to both the Video and the Group object.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Group\",\n \"id\": \"https://example.tld/channel/alice\",\n \"following\": \"https://example.tld/users/alice/following\",\n \"followers\": \"https://example.tld/users/alice/followers\",\n \"name\": \"Alice channel\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n }\n ]\n}\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Video\",\n \"id\": \"https://example.tld/video/123\",\n \"attributedTo\": \"https://example.tld/users/alice\",\n \"name\": \"Let's go live!\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n }\n ]\n}\n"},{"location":"fep/1970/#document-discussion","title":"Document discussion","text":"Some file sharing sofwares allows to have a discussion room attached to files. In such case, the chat links can be added as attachment to Document objects.
"},{"location":"fep/1970/#person-instant-messaging","title":"Person instant messaging","text":"If a user has instant messaging applications, chat links could be added to its Person object, so that we can join him.
If the user has multiple instant messaging accounts (XMPP, Matrix, ...), each on of them can be added, and differenciated by the URI scheme.
"},{"location":"fep/1970/#implementations","title":"Implementations","text":"At the time of the writing, this FEP is been implemented in the Peertube livechat plugin, a chat plugin for Peertube.
"},{"location":"fep/1970/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/1985/","title":"FEP-1985: Signaling how an OrderedCollection is ordered","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT 2024-10-10 #417 Discussions codeberg"},{"location":"fep/1985/#summary","title":"Summary","text":"OrderedCollection is defined as an ordered set in the Activity Vocabulary, but the precise ordering is not defined. The ActivityPub specification requires that instances of OrderedCollection MUST be ordered reverse chronologically by insertion order, but a later errata was proposed to relax this restriction by only applying it to properties defined as OrderedCollection within the ActivityPub specification. Consequently, this allows for some collections to be presented forward chronologically by insertion order, and some collections to be presented reverse chronologically by insertion order. This FEP introduces an orderType property and two vocabulary terms ForwardChronological and ReverseChronological to explicitly signal the ordering of a collection.
https://w3id.org/fep/1985/orderType Label is ordered in Comment Indicates the type of ordering for an OrderedCollection. Domain OrderedCollection Range OrderingClass (@vocab) Required No Functional Yes See also ForwardChronological | ReverseChronological Is defined by FEP-1985 \n\n{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/1985\"\n ],\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/1\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/3\"\n ],\n \"orderType\": \"ForwardChronological\"\n}\n\n \n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/1\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/3\"\n ],\n \"https://w3id.org/fep/1985/orderType\": {\n \"id\": \"https://w3id.org/fep/1985/ForwardChronological\"\n }\n}\n\n OrderingClass URI https://w3id.org/fep/1985/OrderingClass Label OrderingClass Comment Abstract base class for the orderType property's range. See also ForwardChronological | ReverseChronological Is defined by FEP-1985 ForwardChronological URI https://w3id.org/fep/1985/ForwardChronological Label forward chronological order Comment This OrderedCollection is ordered forward chronologically, and adding an item to this collection will add it at the end. Subclass of OrderingClass See also orderType Is defined by FEP-1985 ReverseChronological URI https://w3id.org/fep/1985/ReverseChronological Label reverse chronological order Comment This OrderedCollection is ordered reverse chronologically, and adding an item to this collection will add it at the start. Subclass of OrderingClass See also orderType Is defined by FEP-1985"},{"location":"fep/1985/#examples","title":"Examples","text":"(This section is non-normative.)
Example of a forward chronological OrderedCollection with additional context:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/1985\"\n ],\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/1\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/3\"\n ],\n \"orderType\": \"ForwardChronological\"\n}\n Example of a forward chronological OrderedCollection without additional context:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/1\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/3\"\n ],\n \"https://w3id.org/fep/1985/orderType\": {\n \"id\": \"https://w3id.org/fep/1985/ForwardChronological\"\n }\n}\n Example of a reverse chronological OrderedCollection with additional context:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/1985\"\n ],\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/3\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/1\"\n ],\n \"orderType\": \"ReverseChronological\"\n}\n Example of a reverse chronological OrderedCollection without additional context:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/3\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/1\"\n ],\n \"https://w3id.org/fep/1985/orderType\": {\n \"id\": \"https://w3id.org/fep/1985/ReverseChronological\"\n }\n}\n"},{"location":"fep/1985/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/1985/ForwardChronological/","title":"ForwardChronological","text":"URI : https://w3id.org/fep/1985/ForwardChronological
Label : forward chronological order
Comment : This OrderedCollection is ordered forward chronologically, and adding an item to this collection will add it at the end.
Subclass of : OrderingClass
See also : orderType
Is defined by : FEP-1985
"},{"location":"fep/1985/OrderingClass/","title":"OrderingClass","text":"URI : https://w3id.org/fep/1985/OrderingClass
Label : OrderingClass
Comment : Abstract base class for the orderType property's range.
See also : ForwardChronological | ReverseChronological
Is defined by : FEP-1985
"},{"location":"fep/1985/ReverseChronological/","title":"ReverseChronological","text":"URI : https://w3id.org/fep/1985/ReverseChronological
Label : reverse chronological order
Comment : This OrderedCollection is ordered reverse chronologically, and adding an item to this collection will add it at the start.
Subclass of : OrderingClass
See also : orderType
Is defined by : FEP-1985
"},{"location":"fep/1985/orderType/","title":"orderType","text":"URI : https://w3id.org/fep/1985/orderType
Label : is ordered in
Comment : Indicates the type of ordering for an OrderedCollection.
Domain : OrderedCollection
Range : OrderingClass (@vocab)
Required : No
Functional : Yes
See also : ForwardChronological | ReverseChronological
Is defined by : FEP-1985
"},{"location":"fep/1985/orderType/#examples","title":"Examples","text":"Example of a forward chronological OrderedCollection with additional context
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/1985\"\n ],\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/1\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/3\"\n ],\n \"orderType\": \"ForwardChronological\"\n}\n Example of a forward chronological OrderedCollection without additional context
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/1\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/3\"\n ],\n \"https://w3id.org/fep/1985/orderType\": {\n \"id\": \"https://w3id.org/fep/1985/ForwardChronological\"\n }\n}\n"},{"location":"fep/19b3/","title":"FEP-19b3: Specifying Properties of a Service","text":"Authors Status Date received Tracking issue Discussions Repository Helge DRAFT 2025-11-04 #719 Discussions codeberg"},{"location":"fep/19b3/#summary","title":"Summary","text":"Actors of type Service are used in the Fediverse to represent automated process. In this FEP, we suggest some property values to use to convey further information about the underlying automated process and the responsible parties for the automated process.
Property values can be attached to an actor to specify additional values. Using property values has two key advantage:
The official definition of PropertyValue can be found at PropertyValue - Schema.org Type. The section Examples of Property Value contains examples and further discussions on the usage of property value.
The property values suggested here are meant to provide information to the users of the Fediverse. They are not meant to influence the behavior of Fediverse applications.
"},{"location":"fep/19b3/#source","title":"Source","text":"This property value should provide a link to the source code, e.g.
{\n \"type\": \"PropertyValue\",\n \"name\": \"Source\",\n \"value\": \"https://codeberg.org/helge/release_helper\"\n}\n"},{"location":"fep/19b3/#author","title":"Author","text":"The Author field would specify the author of the source linked in Source. The exact format of the value can be debated. Some possibilities
@helge@mymath.rocks,acct:helge@mymath.rocksuser@domain.example or mailto:user@domain.example)If the author of the service is not the one running it, one needs to use an alternative field. We suggest using Support in this case.
"},{"location":"fep/19b3/#frequency","title":"Frequency","text":"One can run services with a periodic trigger, e.g. cron. This indicates their frequency. Example:
{\n \"type\": \"PropertyValue\",\n \"name\": \"Frequency\",\n \"value\": \"At 42 minutes past the hour\"\n}\n"},{"location":"fep/19b3/#servicetype","title":"ServiceType","text":"Indicates the type of the service, e.g. Comment Tracking System, see FEP-136c. This should be primarily used for services that do more complicated processing, and link to documentation what the service does.
{\n \"type\": \"PropertyValue\",\n \"name\": \"ServiceType\",\n \"value\": \"Comment Tracking System, https://bovine.codeberg.page/comments/136c/fep-136c/\"\n}\n We imagine that specifying Reacts To Mentions would be useful to indicate that this is a bot the answers when mentioned. Furthermore, for bots just as @release, once might specify something like On Event, as they are triggered when continuous integration jobs are run.
"},{"location":"fep/19b3/#examples-of-property-value","title":"Examples of property value","text":"The following example is taken from the actor below
{\n \"type\": \"PropertyValue\",\n \"name\": \"Source\",\n \"value\": \"https://codeberg.org/helge/release_helper\"\n}\n In difference to the example provided by us, mastodon uses HTML as the value of property values, e.g.
{\n \"type\": \"PropertyValue\",\n \"name\": \"\ud83e\udd16\",\n \"value\": \"<a href=\\\"https://botwiki.org\\\" target=\\\"_blank\\\" rel=\\\"nofollow noopener me\\\" translate=\\\"no\\\"><span class=\\\"invisible\\\">https://</span><span class=\\\"\\\">botwiki.org</span><span class=\\\"invisible\\\"></span></a>\"\n}\n This example also shows another behavior, we recommend against: using emojis as name. This has the disadvantage of being awkward to parse (in particular for people using screen readers).
"},{"location":"fep/19b3/#example-of-actor-with-property-value","title":"Example of actor with property value","text":"An example of an actor with property values looks like
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"PropertyValue\": {\n \"@id\": \"https://schema.org/PropertyValue\",\n \"@context\": {\n \"value\": \"https://schema.org/value\",\n \"name\": \"https://schema.org/name\"\n }\n }\n }\n ],\n \"id\": \"https://dev.bovine.social/actor/kH3y9kw8cqRUgzso3oM3_w\",\n \"type\": \"Service\",\n \"preferredUsername\": \"release\",\n \"name\": \"release\",\n \"attachment\": [\n {\n \"type\": \"PropertyValue\",\n \"name\": \"Author\",\n \"value\": \"acct:helge@mymath.rocks\"\n },\n {\n \"type\": \"PropertyValue\",\n \"name\": \"Source\",\n \"value\": \"https://codeberg.org/helge/release_helper\"\n }\n ], \n \"...\": \"...\"\n}\n Note
There are variance with @context which change which URIs property values expand if one uses JSON-LD. As JSON-LD is unused for all practical purposes, this currently does not matter. See the Appendix for details on what goes wrong.
The document
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"PropertyValue\": {\n \"@id\": \"https://schema.org/PropertyValue\",\n \"@context\": {\n \"value\": \"https://schema.org/value\",\n \"name\": \"https://schema.org/name\"\n }\n }\n }\n ],\n \"name\": \"release\",\n \"attachment\": [\n {\n \"type\": \"PropertyValue\",\n \"name\": \"Author\",\n \"value\": \"acct:helge@mymath.rocks\"\n }\n ]\n}\n turns to
{\n \"https://www.w3.org/ns/activitystreams#attachment\": {\n \"@type\": \"https://schema.org/PropertyValue\",\n \"https://schema.org/name\": \"Author\",\n \"https://schema.org/value\": \"acct:helge@mymath.rocks\"\n },\n \"https://www.w3.org/ns/activitystreams#name\": \"release\"\n}\n when compacted against \"@context\": {}. Any variation of @context should behave similarly. The obvious choices
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://schema.org\"\n ],\n \"name\": \"release\",\n \"attachment\": [\n {\n \"type\": \"PropertyValue\",\n \"name\": \"Author\",\n \"value\": \"acct:helge@mymath.rocks\"\n }\n ]\n}\n and with the order of \"https://www.w3.org/ns/activitystreams\" and \"https://schema.org\" reversed lead to incorrect results due to name being overloaded.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/1b12/","title":"FEP-1b12: Group federation","text":"Authors Status Date received Date final Tracking issue Discussions Repository Felix Ableitner me@nutomic.comFINAL 2022-11-12 2023-02-09 #22 - codeberg"},{"location":"fep/1b12/#summary","title":"Summary","text":"Internet forums are one of the oldest forms of social media. This document describes how they are implemented in existing Activitypub platforms using Group actors. It also introduces a new property to indicate that a given object belongs to a group.
Friendica released federated forums in version 2019.03.
Lemmy published the first public beta of federated groups in v0.8.0 (October 2020).
[FEP-400e] introduces publicly appendable collections, which can also be used to implement forums. However they are incompatible with the implementations described here.
"},{"location":"fep/1b12/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this specification are to be interpreted as described in [RFC-2119].
"},{"location":"fep/1b12/#group-actor","title":"Group actor","text":"A federated forum is represented by a Group actor as specified in [Activity Vocabulary]. This actor is not directly controlled by a human, but can perform its main functionality in a fully automated way, essentially acting as a bot. It has the same general properties as any Person actor.
Public groups SHOULD support a standard Follow-Accept workflow. This ensures compatibility with existing implementations which support Person following using the same vocabulary. After receiving a valid Follow activity, the group SHOULD automatically respond with an Accept/Follow, and add the sender to its followers collection. Correspondingly actors can unfollow a group by sending an Undo/Follow activity.
In order to render content in a forum, it is necessary to know which particular forum the content belongs to. This way users can navigate up from a thread to the forum's main page, or view metadata like the description or moderators. With current implementations there is no easy way to determine which forum a given activity or object belongs to, if any.
Currently there are different approaches to specify which group a given object or activity belongs to. Lemmy, Friendica and lotide put the group ID in the to field. Peertube uses attributedTo. Both properties have the problem that they are also used for different purposes by some platforms, and are represented as arrays. So to retrieve the group from a received object, an implementation needs to loop through these properties and resolve each URL, until it hits one which resolves to a Group. Clearly this is very inefficient.
To simplify this process, we propose to specify the group identifier in the audience property. This property is part of Activitystreams, but not yet used in the wild. This way no extension is necessary, and neither will it cause problems for existing implementations. Additionally, platforms can continue to federate the group identifier in the existing format for backwards compatibility.
Each Group actor represents a single forum. Forums contain many user-submitted threads, which can be represented by different objects depending on the implementation. Thread objects SHOULD have a name property which represents the thread title.
Example thread:
{\n \"type\": \"Page\",\n \"id\": \"https://sally.example.org/p/1\",\n \"attributedTo\": \"https://sally.example.org\",\n \"name\": \"Hello forum!\",\n \"audience\": \"https://example.org/my-forum\"\n}\n Threads can have replies, which are usually represented as Notes. They MUST have a property inReplyTo referencing either the thread they belong to, or the parent reply in case of nested replies.
Example reply:
{\n \"type\": \"Note\",\n \"id\": \"https://sally.example.org/p/3\",\n \"attributedTo\": \"https://sally.example.org\",\n \"inReplyTo\": \"https://sally.example.org/p/1\",\n \"content\": \"My first comment\",\n \"audience\": \"https://example.org/my-forum\"\n}\n The properties inReplyTo and audience can be used to navigate up from a reply to a thread and forum. To navigate down from a group to threads and comments, groups MAY have a replies collection which contains all threads. Each thread MAY again have a replies collection which lists all top-level comments responding to the thread.
The main task of a group is to distribute content among its followers.
When a group receives a activity in its inbox, it SHOULD perform some automatic validation, such as checking for domain and user blocks. Groups MAY require additional validation, such as accepting content only from followers, or even manual approval from group moderators. In case an activity fails these checks, the group MAY respond to the sender with a Reject activity.
In case the incoming activity is deemed valid, the group MUST wrap it in an Announce activity, with the original activity as object. The wrapped activity MUST be preserved exactly as it was received, without changing or removing any properties. This ensures that forwarded activities can be verified with [Object Integrity Proofs]. Announce activities SHOULD get added to the group outbox. If the group exposes collections of threads and comments, relevant items should also be added to them.
Example:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.org/my-forum\",\n \"id\": \"example.org/a/5\",\n \"object\": {\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Create\",\n \"id\": \"https://sally.example.org/a/2\"\n \"actor\": \"https://sally.example.org\",\n \"audience\": \"https://example.org/my-forum\",\n \"object\": {\n \"type\": \"Page\",\n \"id\": \"https://sally.example.org/p/1\",\n \"content\": \"Hello forum!\",\n \"audience\": \"https://example.org/my-forum\"\n }\n }\n}\n After the group successfully verifies and wraps the received activity, it sends it to the inboxes of its followers. Followers then use the outer Announce activity to verify that the content was really approved by the group. After this step the Announce can be discarded and only the inner activity shown to users.
This mechanism can be used to publish any possible activity type. Examples include Announce/Like, Announce/Delete/Note or Announce/Undo/Like. Implementations may choose not to forward some activity types which are considered private, for example Follow activities.
Group moderators are those actors who control the group, are able to change its metadata and remove malicious content. They are listed in the group\u2019s attributedTo collection. Moderation is an optional feature, implementations can safely ignore this entire section. At the moment it is only implemented by Lemmy.
{\n \"id\": \"https://example.org/my-forum\",\n \"type\": \"Group\",\n \"name\": \"Ten Forward\",\n \"attributedTo\": \"https://example.org/my-forum/moderators\",\n}\n {\n \"type\": \"OrderedCollection\",\n \"id\": \"https://example.org/my-forum/moderators\",\n \"orderedItems\": [\n \"https://example.org/picard\",\n \"https://example.org/riker\"\n ]\n}\n Group moderators can be changed with Add and Remove activities:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.org/my-forum\",\n \"id\": \"example.org/a/8\",\n \"object\": {\n \"id\": \"https://example.org/a/7\",\n \"type\": \"Add\",\n \"actor\": \"https://example.org/picard\",\n \"object\": \"http://example.org/data\",\n \"audience\": \"https://example.org/my-forum\",\n \"target\": \"https://example.org/my-forum/moderators\"\n }\n}\n The actions which can be done by moderators are called moderation activities. These are implementation specific, examples include Add, Remove (to change the moderators collection), Block (ban malicious users) and Update/Group (change group metadata).
If an group or group follower supports moderation, it MUST validate incoming moderation activities before further processing. Such activities MUST have an actor who is listed in attributedTo. Group followers MUST additionally verify that the moderation activity was announced by the group.
Implementations SHOULD also accept moderation activities which come from the same server where the community is hosted, under the assumption that these are sent by server administrators. These moderation activities also need to be wrapped in Announce by the group.
This document is written based on existing group implementations in Lemmy, Friendica, Hubzilla, Lotide and Peertube. These already federate successfully in production.
The audience field is an exception as it is not in use yet. Lemmy will add support for it in version 0.17.0.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/2100/","title":"Index","text":"hide: - navigation description: \"Historically, after the sudden death of a popular instance, one could neither target groups hosted at it anymore nor contact the whole followers collection to let them know of the new instance housing a certain group. If we always have absolute knowledge of the complete followers collection (or good enough), we can automate based on which instance has more local followers which server would become the new house. Another alternative would be to automatically archive the old group and start again from scratch. This FEP, on the other hand, discusses something very different of automatically moving an actor from one server to a different one. It is about collaboration between different group or organization actors to promote an unified experience between the participants of the linked group actors. We think this may be easier, more flexible, and promote a better UX than only notifying the actor that the house of a certain group has moved, but both solutions would probably achieve similar results in the above use case. This proposal introduces an interpretation of a Group following another Group and the gs unbound attribute. This allow two groups (or organization) to \"act as one\" (not exactly, but elaborated afterwards). This primarily aims at effectively removing a central point of authority for groups, but offers more than that. With this, @alice@undefinedhackers.net can mention a group named hackers (!hackers) or even address an activity To !hackers@instance.gnusocial.test (C2S) and let her instance's !hackers announce to other instances' !hackers. Finally, this proposal is general enough to allow a server to simultaneously have !lug@server (without links), !lug-unbound@server (with the greatest links collection it can grow), and !lug-with-some-links@server (with only some links). It doesn't require linked groups to have the same preferredUsername.\"
"},{"location":"fep/2100/#fep-2100-unbound-group-and-organization","title":"FEP-2100: Unbound Group and Organization","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Diogo Peralta Cordeiro mail@diogo.siteWITHDRAWN 2022-03-31 2025-04-06 #36 Discussions codeberg This FEP wasn't a result of my individual work but rather of the joint effort in this SocialHub discussion and, prior to that, the discussion in GNU social's IRC/XMPP with rozzin (Joshua Judson Rosen) and someonewithpc (Hugo Sales).
"},{"location":"fep/2100/#summary","title":"Summary","text":"Historically, after the sudden death of a popular instance, one could neither target groups hosted at it anymore nor contact the whole followers collection to let them know of the new instance housing a certain group. If we always have absolute knowledge of the complete followers collection (or good enough), we can automate based on which instance has more local followers which server would become the new house. Another alternative would be to automatically archive the old group and start again from scratch.
This FEP, on the other hand, discusses something very different of automatically moving an actor from one server to a different one. It is about collaboration between different group or organization actors to promote an unified experience between the participants of the linked group actors. We think this may be easier, more flexible, and promote a better UX than only notifying the actor that the house of a certain group has moved, but both solutions would probably achieve similar results in the above use case.
This proposal introduces an interpretation of a Group following another Group and the gs:unbound attribute. This allow two groups (or organization) to \"act as one\" (not exactly, but elaborated afterwards).
This primarily aims at effectively removing a central point of authority for groups, but offers more than that. With this, @alice@undefinedhackers.net can mention a group named hackers (!hackers) or even address an activity To !hackers@instance.gnusocial.test (C2S) and let her instance's !hackers announce to other instances' !hackers.
Finally, this proposal is general enough to allow a server to simultaneously have !lug@server (without links), !lug-unbound@server (with the greatest links collection it can grow), and !lug-with-some-links@server (with only some links). It doesn't require linked groups to have the same preferredUsername.
To keep things simple, sometimes you will see things formatted like Activity{Object}. For example, Create{Note} would be a Create activity containing a Note in the object field. Also, we will focus in Actor of type Group, but nothing should stop from using this for Organization.
@nickname@server will be used to refer Actors of type Person or Application.!nickname@server will be used to refer Actors of type Group or Organization.@#!group@server#collection will be used to refer collection collection of !group@server.The key words MAY, MUST, MUST NOT, SHOULD, and SHOULD NOT are to be interpreted as described in [RFC2119].
"},{"location":"fep/2100/#links-between-groups-terminology","title":"Links between Groups terminology","text":""},{"location":"fep/2100/#activitystreams-20-requirements-for-this-mechanism","title":"ActivityStreams 2.0 requirements for this mechanism","text":""},{"location":"fep/2100/#example-group-actor-in-this-fep","title":"Example Group Actor in this FEP","text":"{\n \"type\": \"Group\",\n \"streams\": [],\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"gs\": \"https://www.gnu.org/software/social/ns#\"\n },\n {\n \"unbound\": {\n \"@id\": \"gs:unbound\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"id\": \"https://instance.gnusocial.test/group/hackers\",\n \"unbound\": true,\n \"preferredUsername\": \"hackers\",\n \"endpoints\": {\n \"sharedInbox\": \"https://instance.gnusocial.test/inbox.json\"\n },\n \"inbox\": \"https://instance.gnusocial.test/group/hackers/inbox.json\",\n \"outbox\": \"https://instance.gnusocial.test/group/hackers/outbox.json\",\n \"following\": \"https://instance.gnusocial.test/group/hackers/subscriptions\",\n \"followers\": \"https://instance.gnusocial.test/group/hackers/subscribers\",\n}\n"},{"location":"fep/2100/#creating-a-link-between-two-group-actors","title":"Creating a link between two group actors","text":"Creating a directed link between two group actors is just a regular Follow request between any two actors.
Assume that !hackers@instance.gnusocial.test sends a Follow request to !lug@gnusocial.net.
If gs:unbound: false or not present, then if !lug@gnusocial.net accepts the Follow request, it will Announce{*} entering its inbox to !hackers@instance.gnusocial.test.
If gs:unbound: true, then !lug@gnusocial.net will both accept the Follow request and submit a Follow request of its own to !hackers@instance.gnusocial.test.
If both !hackers@instance.gnusocial.test and !lug@gnusocial.net have added each other to their linksTo, they will act as if they were the same group. If they have equivalent groupLinks collections, then they are essentially fully mirrored groups.
Note that the \"Link negotiation\" happens between two Group actors (S2S).
"},{"location":"fep/2100/#some-scenarios","title":"Some scenarios","text":""},{"location":"fep/2100/#1-group-a-follows-group-b-which-has-gsunbound-false","title":"1. Group A follows Group B which hasgs:unbound = false","text":"gs:unbound = true","text":"gs:unbound = true.gs:unbound attribute","text":"!hackers@C: Announce{Note} TO !hackers@[B] (S2S)!hackers@C as well.CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/2277/","title":"FEP-2277: ActivityPub core types","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT informational 2025-01-31 #485 Discussions codeberg"},{"location":"fep/2277/#summary","title":"Summary","text":"Classification of ActivityPub objects based on their shape.
"},{"location":"fep/2277/#rationale","title":"Rationale","text":"ActivityPub applications often have different processing rules for actors, activities, collections and other objects. In most cases, the class of an object can be inferred from its context: object delivered to inbox is expected to be an activity, and the value of its actor property is expected to be an actor.
However, the class can not always be inferred from context. In some cases, only the object ID is known, such as when it is provided by a user. Embeddings may also be ambiguous:
object of Update activity can be an object or an actor.object of Announce activity can be an object or an activity.Applications may use the type property to determine object's class, but that hinders interoperabilty because such applications would not be able to process objects with unknown types. Therefore, a different approach would be preferable.
Activity Streams 2.0 standard defines 8 core object types:
ObjectLinkActivityIntransitiveActivityCollectionOrderedCollectionCollectionPageOrderedCollectionPageUnfortunately, definitions provided in the specification are not precise. Only Object and Link are defined as disjoint types, meaning an object could be an Activity and a Collection at the same time. \"Actors\" are described as specializations of Object, but there is no corresponding Actor core type.
The lack of good definitions and the exclusion of the Actor type make standard classification unsuitable for practical purposes. Therefore, applications may need to use a different classification.
One way to divide objects into distinct classes is to look at their properties and their connections to other objects (indicated by their properties). This approach can be used to define 7 core types:
Actor: an entity that publishes and receives activities.Activity: an action taken by an actor.Collection: a container for other objects (a collection or a collection page).VerificationMethod: a verification method.PublicKey: a public key (a legacy form of a verification method).Link: a link.Object: all other objects.The next section specifies an algorithm that classifies any ActivityPub object as one of these core types by analyzing the object's shape. This technique is often referred to as duck typing.
"},{"location":"fep/2277/#duck-typing","title":"Duck typing","text":"The following algorithm can be used to determine the core type of the object:
inbox and outbox properties, return Actor.publicKeyMultibase property, return VerificationMethod.publicKeyPem property, return PublicKey.href property, return Link.actor property, return Activity.items, orderedItems, totalItems, partOf, first, last, next, prev or current property, return Collection.Object.Application of this algorithm results in non-overlapping core types. For example, an actor with items property is still an actor and not a collection.
The value of type property is not taken into consideration.
Warning
ActivityPub standard requires actors to have both inbox and outbox properties, but in practice outbox is not always present. If compatibility with non-conformant implementations is desirable, step #1 can be changed to \"If object has inbox property, return Actor\".
Warning
Pleroma adds an actor property to objects that are not activities. To make an allowance for that, the step #5 of the algorithm can be changed to \"If object has an actor property, and doesn't have an attributedTo property, return Activity\".
The output of the algorithm might be different in LD-aware and in LD-unaware applications due to a possibility of re-mapping of terms in @context.
This may pose a security risk.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"foo\": \"as:inbox\",\n \"bar\": \"as:outbox\"\n }\n ],\n \"type\": \"Note\",\n \"id\": \"https://social.example/note\",\n \"foo\": \"https://social.example/inbox\",\n \"bar\": \"https://social.example/outbox\"\n}\n"},{"location":"fep/2277/#alternatives-considered","title":"Alternatives considered","text":""},{"location":"fep/2277/#multi-typing","title":"Multi-typing","text":"The alternative to duck typing is to use multiple types. For example, this object can be unambiguously identified as an Activity:
{\n \"type\": [\"Bite\", \"Activity\"]\n}\n However, existing implementations don't add a second type, and even if changing all of them were possible, duck typing would still need to be used as a fallback during the transitional period.
"},{"location":"fep/2277/#type-hierachies","title":"Type hierachies","text":"The core type of an object can be determined via the definition of its type in a vocabulary, but that means all ActivityPub applications would have to support JSON-LD.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/22b6/","title":"FEP-22b6: Linking an ActivityPub Object to a HTML page and back","text":"Authors Status Date received Tracking issue Discussions Repository Helge @helge@mymath.rocksDRAFT 2025-11-12 #730 Discussions codeberg"},{"location":"fep/22b6/#summary","title":"Summary","text":"Links are a fundamental part of the internet. This FEP describes how to use links to link a HTML page to an ActivityPub object. The mechanisms described in this document are not new and are used to link to RSS feeds (see alternate, second example).
"},{"location":"fep/22b6/#linking-from-a-html-page","title":"Linking from a HTML page","text":"The HTML living standard states about alternate
The keyword creates a hyperlink referencing an alternate representation of the current document.
In this sense, we can link from the HTML page to an ActivityPub object using
<head>\n ...\n <link rel=\"alternate\" type=\"application/activity+json\" href=\"http://location.example/object.id\">\n ...\n</head>\n which can be used for automatic discovery. To provide a link for the user, one should use an anchor, e.g.
<body>\n ...\n <a rel=\"alternate\" type=\"application/activity+json\" href=\"http://location.example/object.id\">\n ActivityPub Object\n </a>\n ...\n</body>\n We recommend using both forms as they serve different purposes.
Warning
One might be tempted to use canonical instead of alternate if the HTML page just renders the content of the ActivityPub object. The author believes that sticking to the single format giving here has the highest chance of leading to widespread support.
"},{"location":"fep/22b6/#discussions","title":"Discussions","text":"As the example below shows, there are use cases when the ActivityPub object and the page are on different servers. One can however still check the relationship, by verifying the ActivityPub object contains a link back to the page.
Similarly, the ActivityPub object might provide less content than the webpage.
"},{"location":"fep/22b6/#example","title":"Example","text":"The page https://bovine.codeberg.page/comments/ contains the link element
<link rel=\"alternate\"\n href=\"https://comments.bovine.social/pages/aHR0cHM6Ly9ib3ZpbmUuY29kZWJlcmcucGFnZS9jb21tZW50cy8=\"\n type=\"application/activity+json\">\n and the linked object resolves to
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Page\",\n \"attributedTo\": \"https://comments.bovine.social/actor/rF4xnx1QraAIU3Krg-2Qzg\",\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\"\n ],\n \"published\": \"2025-10-20T17:45:11Z\",\n \"url\": [\n {\n \"type\": \"Link\",\n \"href\": \"https://bovine.codeberg.page/comments/\",\n \"mediaType\": \"text/html\"\n }\n ],\n \"name\": \"Comment Tracking System\",\n \"summary\": null,\n \"id\": \"https://comments.bovine.social/pages/aHR0cHM6Ly9ib3ZpbmUuY29kZWJlcmcucGFnZS9jb21tZW50cy8=\",\n \"context\": \"https://comments.bovine.social/pages/aHR0cHM6Ly9ib3ZpbmUuY29kZWJlcmcucGFnZS9jb21tZW50cy8=/context\",\n \"replies\": \"https://comments.bovine.social/pages/aHR0cHM6Ly9ib3ZpbmUuY29kZWJlcmcucGFnZS9jb21tZW50cy8=/replies\",\n \"likes\": \"https://comments.bovine.social/pages/aHR0cHM6Ly9ib3ZpbmUuY29kZWJlcmcucGFnZS9jb21tZW50cy8=/likes\",\n \"shares\": \"https://comments.bovine.social/pages/aHR0cHM6Ly9ib3ZpbmUuY29kZWJlcmcucGFnZS9jb21tZW50cy8=/shares\"\n}\n The Page object can be understood as Page in the ActivityVocabulary here, i.e. \"Represents a Web Page\".
"},{"location":"fep/22b6/#alternatives","title":"Alternatives","text":"As already said, we recommend using this suggested approach to link from the HTML to an ActivityPub object.
However, there are other methods that are in use to provide the link, and should be considered valid options.
For the use case of FEP-136c: Comment Tracking Services, that motivates this work, embedding the links in the HTML is the only viable option. This is due to the HTML often being hosted on static sites that do not provide the option to use Content Negotiation or Link Headers.
"},{"location":"fep/22b6/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/2677/","title":"FEP-2677: Identifying the Application Actor","text":"Authors Status Date received Tracking issue Discussions Repository Helge helge.krueger@gmail.comDRAFT 2023-10-14 #190 - codeberg"},{"location":"fep/2677/#summary","title":"Summary","text":"It is a common pattern in Fediverse applications to have a special actor of type Application. This is for example the actor at https://mastodon.example/actor for Mastodon or at https://pleroma.example/internal/fetch for Pleroma. This application actor can be fetched with an unsigned request, so it is possible to use it to fetch public keys.
The goal of this FEP is to provide an explicit mechanism of identifying the application actor, with the goal of making it usable for further tasks, e.g.
We will first define, what we mean by an application actor. We will impose two requirements on them
As already mentioned application actors are currently used by most Fediverse applications to fetch public keys. See here for a list of examples.
"},{"location":"fep/2677/#identifying-the-application-actor-with-nodeinfo","title":"Identifying the Application Actor with nodeinfo","text":"In [NodeInfo], see also [FEP-f1d5], the well-known path /.well-known/nodeinfo is defined and it is specified that a document in JRD format [RFC 7033] is served there.
The requirement of the current FEP is that the /.well-known/nodeinfo contains an additional link with relation type https://www.w3.org/ns/activitystreams#Application, which resolves to an application actor as described in the previous section.
We note that it is not necessary to implement the relations specified in [NodeInfo] to satisfy the current FEP.
"},{"location":"fep/2677/#example","title":"Example","text":"We will consider the server with domain node.example. This means that a request to https://node.example/.well-known/nodeinfo will resolve to
{\n \"links\": [\n {\n \"rel\": \"http://nodeinfo.diaspora.software/ns/schema/2.0\",\n \"href\": \"https://node.example/nodeinfo/2.0\"\n },\n {\n \"rel\": \"https://www.w3.org/ns/activitystreams#Application\",\n \"href\": \"https://node.example/actor\"\n }\n ]\n }\n Next, a request to https://node.example/actor with accept header application/activity+json could return
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/v1\",\n ],\n \"id\": \"https://node.example/actor\",\n \"type\": \"Application\",\n \"inbox\": \"https://node.example/actor/inbox\",\n \"outbox\": \"https://node.example/actor/outbox\",\n \"publicKey\": {\n \"id\": \"https://node.example/actor#main-key\",\n \"owner\": \"https://node.example/actor\",\n \"publicKeyPem\": \"-----BEGIN PUBLIC KEY-----\\n....\\n-----END PUBLIC KEY-----\\n\"\n }\n}\n"},{"location":"fep/2677/#discussion","title":"Discussion","text":"The approach of this FEP has been chosen to have a light touch. Some alternative choices would be:
Both options have the downside that it would require to introduce a new path, which forces every implementation to implement it in a similar way. Instead, we reuse an existing path, and the implementer is free to choose where to put the application actor.
The second question is why fix the type Application and not Service. First, it is consistent with most current implementations. Second, Mastodon uses Service for bot accounts. So it feels like a good idea to start distinguishing from this usage. We would phrase this distinction as follows:
Application is triggered by events in the application, e.g. a signed request and then fetching the corresponding public key.Service is triggered by either activities arriving at its inbox or external events, e.g. a timer. This means an actor of type Service is similar to one controlled by an user.These are not hard rules on when to use Application or Service. They will probably break down as more complicated Fediverse implementations are build. We hope that they can serve as a level of guidance, how to differentiate actors.
https://bovine.example/activitypub/bovine Firefish https://firefish.example/actor Lemmy https://lemmy.example/ Mastodon https://mastodon.example/actor Mitra http://mitra.example/actor Pleroma https://pleroma.example/internal/fetch Mbin https://mbin.example/i/actor WordPress https://wordpress.example/wp-json/activitypub/1.0/application Mobilizon https://mobilizon.example/relay Gancio https://gancio.example/federation/u/<instance_name> Friendica https://friendica.example/ PeerTube https://peertube.example/accounts/peertube Pixelfed https://pixelfed.example/i/actor Note: Feel free to add further links.
"},{"location":"fep/2677/#implementations","title":"Implementations","text":"Software dateImplemented dateReleased WordPress 2023-12-21 - Mobilizon 2023-12-14 - Gancio 2023-12-22 -"},{"location":"fep/2677/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/268d/","title":"FEP-268d: Search consent signals for objects","text":"Authors Status Date received Tracking issue Discussions Repository Daiki \"tesaguri\" Mizukami tesaguriguma+fep-268d@gmail.comDRAFT 2024-10-12 #421 Discussions codeberg"},{"location":"fep/268d/#summary","title":"Summary","text":"This FEP documents an extension property for [Activity Streams 2.0] to signal the consent for an object to be searched by a given actor.
"},{"location":"fep/268d/#history","title":"History","text":"This section is non-normative.
Fedibird introduced the searchableBy property in August 2022.
kmyblue implemented the searchableBy property in April 2023.
Mastodon began implementing the indexable property [FEP-5feb] in February 2023 and released it in September 2023.
This section is non-normative.
[FEP-5feb] introduced the indexable property, which allows an Activity Streams actor to signal their preference for whether objects attributed to them should be made publicly available for search.
The indexable property is an actor-level property which takes a boolean value, allowing the actor to make all of their objects available for search by either anyone or no one at all. However, sometimes it is desirable to make a certain set of objects searchable/unsearchable or make an object searchable by a limited audience. The object-level searchableBy property documented in this FEP complements the indexable property with a more fine-grained audience targeting ability.
The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"NOT RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in [RFC 2119].
"},{"location":"fep/268d/#terms-defined","title":"Terms defined","text":"searchableBy URIhttp://fedibird.com/ns#searchableBy Label searchable by Comment Identifies one or more entities that are allowed to search for the subject. Domain Object Range Object | Link Required No Is defined by FEP-268d A non-normative example of a publicly searchable post: {\n \"@context\": [\n \"https://w3id.org/fep/268d\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"id\": \"https://example.com/notes/1\",\n \"attributedTo\": \"https://example.com/users/1\",\n \"to\": \"https://www.w3.org/ns/activitystreams#Public\",\n \"cc\": \"https://example.com/users/1/followers\",\n \"type\": \"Note\",\n \"content\": \"Hello, world!\",\n \"searchableBy\": \"https://www.w3.org/ns/activitystreams#Public\"\n}\n A post only searchable by the mentioned users and the author's followers: {\n \"@context\": [\n \"https://w3id.org/fep/268d\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"id\": \"https://example.com/notes/42\",\n \"attributedTo\": \"https://example.com/users/1\",\n \"to\": \"https://alice.example/actor\",\n \"cc\": [\"https://example.com/users/1/followers\", \"https://www.w3.org/ns/activitystreams#Public\"],\n \"type\": \"Note\",\n \"content\": \"@Alice Happy birthday!\",\n \"tag\": {\n \"type\": \"Mention\",\n \"href\": \"https://alice.example/actor\",\n \"name\": \"@Alice\"\n },\n \"searchableBy\": [\"https://alice.example/actor\", \"https://example.com/users/1/followers\"]\n}\n (where `https://example.com/users/1/followers` is the `followers` collection of `https://example.com/users/1`.) A post only searchable by the author themselves: {\n \"@context\": [\n \"https://w3id.org/fep/268d\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"id\": \"https://example.com/notes/123\",\n \"attributedTo\": \"https://example.com/users/1\",\n \"to\": \"https://example.com/users/1/followers\",\n \"cc\": \"https://www.w3.org/ns/activitystreams#Public\",\n \"type\": \"Note\",\n \"summary\": \"Note to self\",\n \"searchableBy\": \"https://example.com/users/1\"\n}\n An actor with a default searchability of `as:Public`: {\n \"@context\": [\n \"https://w3id.org/fep/268d\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"id\": \"https://example.com/users/1\",\n \"type\": \"Person\",\n \"searchableBy\": \"https://www.w3.org/ns/activitystreams#Public\"\n}\n ## Searchability of objects This section describes how the searchability of an object for a given actor should be determined. If the object has at least one `searchableBy` property value and the searching actor isn't included in the property values, the object MUST NOT be made available for search by that actor. Even in this case, the object SHOULD be made available for search if the object is attributed to the searching actor themselves, and MAY be made available for search if the searching actor has previously interacted with the object (e.g. by `Like`-ing the object). If the `searchableBy` property values of the object include the searching actor, the object SHOULD be made available for search by the actor, unless the searching actor is otherwise forbidden to see the object by access controls such as the Activity Streams audience targeting (`to`, `bto`, `cc`, `bcc` and `audience` properties) of the object and `Block`s by the object's attributed actors. Objects with a `searchableBy` value of `https://www.w3.org/ns/activitystreams#Public` SHOULD be made available for search by any users. If the object does not have any `searchableBy` values but is attributed to an actor with at least one `searchableBy` value, the object SHALL be treated as inheriting the `searchableBy` value of the attributed actor. Note that, in JSON-LD, an empty array value (`[]`) is semantically equivalent to `null` or \"undefined\". In consequence, if a publisher wishes to make an object searchable by no one (instead of falling back on the attributed actor's `searchableBy` value), a placeholder object needs to be used as the `searchableBy` value of that object. The RECOMMENDED placeholder in that case is the object's `attributedTo` value, if any. If the object does not fall under any of the conditions described in this section, the searchability of the object MAY be determined by an implementation-defined strategy. Note that the searchability signaled by the `searchableBy` property is only applicable to Activity Streams representation of objects. Searchability of non-Activity Streams representations (most notably, HTML) of objects with non-public `searchableBy` property value SHOULD be signaled by other means. Non-normative examples of such means include the Robots Exclusion Protocol [RFC-9309] [ROBOTSTXT], the `` HTML tag [ROBOTSTXT-META] and the `X-Robots-Tag` HTTP header [GOOGLE-ROBOTS]. ## Interaction with FEP-5feb (`toot:indexable`) *This section is only applicable to conformant [FEP-5feb] implementations.* If an object does not have any `searchableBy` values and is attributed to an actor with no `searchableBy` values but with an `http://joinmastodon.org/ns#indexable` value, the searchability of the object SHOULD be determined according to [FEP-5feb]. If an object has at least one `searchableBy` value, the property SHALL take precedence over the `indexable` property. In particular, if an object is not searchable according to the `searchableBy` property, the object must not be made available for search, even if the object is targeted to the `as:Public` collection and the object is attributed to an actor with `\"indexable\": true`. ## Security considerations *This section is non-normative.* As mentioned in the normative section, consuming implementations should take care to ignore JSON entries with an empty array value (`\"searchableBy\": []`). Failure to do so would have a security implication if the implementation is verifying RDF-based signatures of objects, because the empty array value does not affect the RDF dataset (and hence the resulting signature value), so that an attacker can remove/insert a `\"searchableBy\": []` entry from/to a signed object without making the signature verification fail, allowing them to tamper with the object's searchability if the empty array value were handled differently from \"undefined\". ## Implementations *This section is non-normative.* - [Fedibird](https://github.com/fedibird/mastodon/tree/fedibird) - [kmyblue](https://github.com/kmycode/mastodon) ## References - [Activity Streams 2.0] James M Snell, Evan Prodromou, [Activity Streams 2.0](https://www.w3.org/TR/activitystreams-core/), 2017 - [FEP-5feb] Claire, [FEP-5feb: Search indexing consent for actors](https://w3id.org/fep/5feb), 2023 - [RFC-2119] S. Bradner, [Key words for use in RFCs to Indicate Requirement Levels](https://doi.org/10.17487/RFC2119), 1997 - [RFC-9309] M. Koster, [Robots Exclusion Protocol](https://doi.org/10.17487/RFC9309), 2022 - [ROBOTSTXT] Martijn Koster, [A Standard for Robot Exclusion](https://www.robotstxt.org/orig.html), 1994 - [ROBOTSTXT-META] Martijn Koster, [About the Robots \\ tag](https://www.robotstxt.org/meta.html), 2007 - [GOOGLE-ROBOTS] Google, [Robots Meta Tags Specifications](https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#xrobotstag) ## Copyright CC0 1.0 Universal (CC0 1.0) Public Domain Dedication To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work."},{"location":"fep/2931/","title":"Index","text":"hide: - navigation description: \"FEP-7888 attempts to lay out clarifications for the use of the context property based on rationale and history, in which context is used primarily to logically group objects related by their \"context\", or in other words, stating that some object \"was created in relation to\" another object, where the latter object denotes some purpose for the first object. In response to FEP-7888, and motivated by the desire to backfill entire conversations, various softwares wishing to federate have chosen to directly represent context as a Collection of objects acknowledged to be \"within\" some canonical context collection. This FEP describes this approach, its usages, and some drawbacks.\"
"},{"location":"fep/2931/#fep-2931-representing-context-with-a-collection","title":"FEP-2931: Representing context with a Collection","text":"Authors Status Type Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT implementation 2025-03-22 #534 Discussions codeberg"},{"location":"fep/2931/#summary","title":"Summary","text":"FEP-7888 attempts to lay out clarifications for the use of the context property based on rationale and history, in which context is used primarily to logically group objects related by their \"context\", or in other words, stating that some object \"was created in relation to\" another object, where the latter object denotes some purpose for the first object.
In response to FEP-7888, and motivated by the desire to backfill entire conversations, various softwares wishing to federate have chosen to directly represent context as a Collection of objects acknowledged to be \"within\" some canonical context collection. This FEP describes this approach, its usages, and some drawbacks.
FEP-7888 describes the concept of \"Canonical collections of objects within an authoritative context\" as a case where, if a context dereferences to some object, then the type of that resolved object can indicate a certain relation as representing the \"canonical collection\". In the case of this FEP, the \"canonical collection\" is the Collection itself. This gives us the following normative description:
context property is dereferenceable to an object that is a Collection or OrderedCollection, the canonical context collection is id.(This section is non-normative.)
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://posts.example/1\",\n \"type\": \"Note\",\n \"context\": {\n \"id\": \"https://conversation.example\",\n \"type\": \"OrderedCollection\",\n \"items\": [\n \"https://posts.example/1\",\n \"https://posts.example/2\",\n \"https://posts.example/5\"\n ]\n }\n}\n"},{"location":"fep/2931/#consuming-a-context-collection","title":"Consuming a context Collection","text":"(This section is non-normative.)
The context represented by a Collection can be processed (for example, for backfill purposes) by iteratively processing its items (or orderedItems if expressed using that JSON-LD term) and considering them to be logically grouped together.
(This section is non-normative.)
The protocol for becoming aware of or discovering objects claiming to be included within your context are out of scope for this FEP. However, if you encounter an object claiming your context, then it can be Added directly to the Collection.
"},{"location":"fep/2931/#drawbacks","title":"Drawbacks","text":"(This section is non-normative.)
"},{"location":"fep/2931/#collections-cannot-be-cleanly-followed-via-c2s-outbox-delivery","title":"Collections cannot be cleanly followed via C2S outbox delivery","text":"Using a Collection type forcibly opts that object into certain spec behaviors defined by ActivityPub. Namely, if you are using a context collection, and you wish to make your Collection followable, the naive approach is to make it into an ActivityPub actor by giving it an inbox that can at least respond to Follow activities with an Accept Follow. However, there is an issue with the outbox delivery algorithm defined in the ActivityPub client-to-server API, wherein the inbox discovery algorithm necessarily always attempts to iterate over every single item in the Collection, not just the Collection itself. This behavior is triggered whenever an ActivityPub outbox receives a payload with an addressing property such as to, cc, bto, bcc, or audience. For a context Collection that is also an AP actor, addressing this Collection can be unintentionally costly to the server performing delivery, as this server is expected to attempt iteration over potentially unbounded numerous items from the Collection.
For example, given the following activity:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://activity.example\",\n \"type\": \"Announce\",\n \"object\": \"https://posts.example/7\",\n \"to\": \"https://conversation.example\"\n}\n The intention might be to only POST to the inbox of https://conversation.example (and thus notify the context Collection of https://posts.example/7 existing), but the outbox delivery algorithm as currently defined at the time of writing this FEP will instead attempt delivery to all of the following inbox properties it can find across the following objects:
https://conversation.examplehttps://posts.example/1https://posts.example/2https://posts.example/5There is no way to opt out of this \"Collection expansion\" behavior. Consequently, following a context Collection cannot be done via naive ActivityPub. See FEP-efda: Followable objects for a potential workaround, where the Follow activity is addressed to attributedTo and sent to attributedTo.inbox instead of being addressed directly to id and delivered to id.inbox.
This problem does not occur if the ActivityPub \"POST to outbox\" is not used, and instead the Follow activity is delivered directly with Linked Data Notifications \"POST to inbox\".
When modeling explicit concepts such as a \"conversation\", there is a difference between saying that \"a conversation is a collection of posts\", versus saying that \"a conversation has a collection of posts\". The former relation (\"is a\") may be imprecise or may prevent declaring other relations in a way that makes sense. The latter relation (\"has a\") allows for declaring properties of the Conversation that are not properties of the collection of posts instead.
"},{"location":"fep/2931/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/2c59/","title":"FEP-2c59: Discovery of a Webfinger address from an ActivityPub actor","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@prodromou.nameDRAFT 2024-01-04 #226 - codeberg"},{"location":"fep/2c59/#summary","title":"Summary","text":"[Webfinger][Webfinger] is used on the fediverse to abstract out variations in [ActivityPub][ActivityPub] actor URL formats, giving a uniform way of addressing an actor. With a Webfinger address, a client can discover the actor's ActivityPub actor URL. This specification defines an explicit way to reverse the process, and discover a preferred Webfinger address from an ActivityPub actor URL.
"},{"location":"fep/2c59/#motivation","title":"Motivation","text":"[ActivityPub][ActivityPub] is a protocol for federated social networking. It uses HTTPS URLs to identify actors, that is, the people, organizations, applications and groups that use the protocol.
The URL format is not standardized, and there are many variations. For example, a user with the username evan on the activitypub.example domain could have an actor URL of:
https://activitypub.example/users/evanhttps://activitypub.example/users/1234https://social.activitypub.example/evanhttps://evan.activitypub.example[Webfinger][Webfinger] is a discovery protocol for people on the Internet. A Webfinger address is an email-address-like identity in the form user@activitypub.example. Following the Webfinger protocol, a client can discover information about the person at that address, including their ActivityPub actor URL.
This discovery is unidirectional and can cross domain boundaries. This allows potentially misleading or harmful use of WebFinger to identify actors on the network without their consent. For example, the owner of idiot.example could create a Webfinger address pompous@idiot.example that points to https://activitypub.example/users/evan.
In addition, ActivityPub servers that display actors' Webfinger addresses in their UIs need a reliable way to know which Webfinger is preferred for a given actor ID.
To solve this problem, [Mastodon][Mastodon] constructs a canonical Webfinger address for an actor by extracting the preferredUsername from the actor's profile, and prepending it to the domain name of the actor's identity URL. It then does a Webfinger lookup on that address, and if the actor URL returned matches the identity URL, it uses that Webfinger address.
This technique can cause issues for certain actor URLs. First, preferredUsername is not a required property for an ActivityPub actor.
Second, it becomes inconvenient to use subdomains or alternate domains for actor URLs. If the original WebFinger is evan@organization.example, and the ActivityPub URL is https://social.organization.example/evan, the Webfinger will be canonicalized to the longer evan@social.organization.example. A similar problem arises if the actor URL uses one subdomain per user, like https://evan.organization.example/.
This specification defines a way to discover a Webfinger address from an ActivityPub actor without constructing it from properties and URL parsing. It gives domain owners the option to use a registered domain for their Webfinger addresses, and host their ActivityPub server on a subdomain or a different domain. The Mastodon method can be used as a fallback.
"},{"location":"fep/2c59/#specification","title":"Specification","text":"webfinger property, which is a canonical Webfinger address for the actor.webfinger property is functional, that is, there can be at most one webfinger property per actor.webfinger property MUST link directly to the actor URL verbatim, without redirects or aliases.webfinger property SHOULD be a plain Webfinger address, like user@domain.example.webfinger property MAY be an acct: URL, like acct:user@domain.example.The context document for this specification is https://purl.archive.org/socialweb/webfinger. Its contents are as follows:
{\n \"@context\": {\n \"wf\": \"https://purl.archive.org/socialweb/webfinger#\",\n \"xsd\": \"http://www.w3.org/2001/XMLSchema#\",\n \"webfinger\": {\n \"@id\": \"wf:webfinger\",\n \"@type\": \"xsd:string\"\n }\n }\n}\n"},{"location":"fep/2c59/#examples","title":"Examples","text":"A publisher can include the webfinger property for an actor.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/webfinger\"\n ],\n \"id\": \"https://social.example.com/evanp\",\n \"type\": \"Person\",\n \"name\": \"Evan Prodromou\",\n \"inbox\": \"https://social.example.com/evanp/inbox\",\n \"outbox\": \"https://social.example.com/evanp/outbox\",\n \"following\": \"https://social.example.com/evanp/following\",\n \"followers\": \"https://social.example.com/evanp/followers\",\n \"liked\": \"https://social.example.com/evanp/liked\",\n \"webfinger\": \"evanp@example.com\"\n}\n A publisher can include the webfinger property for a Person object referenced in the actor property of an activity.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/webfinger\"\n ],\n \"id\": \"https://social.example.com/evanp/activity/775\",\n \"to\": [\"Public\"],\n \"type\": \"Like\",\n \"summary\": \"Evan liked a note\",\n \"actor\": {\n \"type\": \"Person\",\n \"name\": \"Evan Prodromou\",\n \"id\": \"https://social.example.com/evanp\",\n \"webfinger\": \"evanp@example.com\"\n },\n \"object\": {\n \"type\": \"Note\",\n \"id\": \"https://social.example.com/other/note/221\",\n \"to\": [\"Public\"]\n }\n}\n The publisher can include the webfinger property for an actor with an acct: prefix.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/webfinger\"\n ],\n \"id\": \"https://prefix.example/urlfan\",\n \"type\": \"Person\",\n \"name\": \"URL Fan\",\n \"inbox\": \"https://prefix.example/urlfan/inbox\",\n \"outbox\": \"https://prefix.example/urlfan/outbox\",\n \"following\": \"https://prefix.example/urlfan/following\",\n \"followers\": \"https://prefix.example/urlfan/followers\",\n \"liked\": \"https://prefix.example/urlfan/liked\",\n \"webfinger\": \"acct:urlfan@prefix.example\"\n}\n"},{"location":"fep/2c59/#security-considerations","title":"Security Considerations","text":"The source of the webfinger property is important. A third party may spoof, intentionally or unintentionally, the webfinger property of another actor. The property should only be treated as the preferred Webfinger address for the actor if it comes from the actor id URL, or if it is delivered with the actor's [HTTP Signature][HTTP Signature] via the ActivityPub protocol.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/2e40/","title":"FEP-2e40: The FEP Vocabulary Extension Process","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Helge Krueger helge.krueger@gmail.comWITHDRAWN 2023-02-13 2023-09-04 #62 - codeberg"},{"location":"fep/2e40/#summary","title":"Summary","text":"Current usage of ActivityPub relies on the ActivityStreams namespace [AS-NS] combined with custom extensions [Mastodon NS]. As far as I can tell, no best practices exist or a formal process to add new namespaces.
This FEP will
Note: Withdrawn as no longer compatible with the FEP repository structure, see this issue.
"},{"location":"fep/2e40/#background-and-terminology","title":"Background and Terminology","text":"The JSON-LD context is introduced in 3.1 The Context of [JSON-LD]. The context of an object is specified by its @context property.
One can think of the context as defining certain strings to be equivalent. For example Note, as:Note, and https://www.w3.org/ns/activitystreams#Note all represent the same thing. More details can be found in 3.2. IRIs. Following [JSON-LD], we will refer to all three strings mentioned above as a Term. The focus here is on having a format that is machine-readable. Having a standardized format allows various algorithms to be applicable see [Canon] and [LD-API].
The second useful aspect of this is that one can define the used terms through the provided URL: https://www.w3.org/ns/activitystreams#Note. Clicking on it will let you easily find the definiton of the Note Type. This format is human-readable.
We will refer to the combination of Context and easily accessible documentations for the terms a Vocabulary.
"},{"location":"fep/2e40/#requirements","title":"Requirements","text":"The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].
"},{"location":"fep/2e40/#the-fep-vocabulary","title":"The FEP Vocabulary","text":"We now formalize the process of creating an adding to the FEP Vocabulary. This section formalizes the location of the two files containing the machine readable json-ld context and human readable markdown description. How to add new terms will be discussed in the next section.
This FEP creates the file /feps/assets/fep-2e40/namespace.json with content
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"fep\": \"https://w3id.org/fep#\"\n }\n ]\n}\n A description of this context is contained in /feps/assets/fep-2e40/namespace.md. The goal of inclusion of the ActivityStreams context is to increase the chances of most objects having the simple form
{\n \"@context\": \"https://w3id.org/fep\",\n ...\n}\n which is as close as one can get to pure JSON with JSON-LD. These two files form the FEP Vocabulary. Through a pull-request to [W3-ID], these two files will be made available at https://w3id.org/fep.
The authors of a new FEP-xxxx following [FEP-a3ed] MAY include a section or sections titled \"Create FEP Term: $TERM\". The $TERM MUST follow the following rules inspired by points 2-5 of 3. The Registration Process in [DID-Reg].
Furthermore, the author MAY perform the following action
fep-xxxx-$TERM to /feps/assets/fep-2e40/namespace.json./feps/assets/fep-2e40/namespace.md.The changes to the two files MUST be described in the FEP, and they MUST only add new content or change content added by the FEP. An example is provided in the next section. While these actions are optional at the DRAFT stage of a FEP, they MUST be performed before a FEP can reach its FINAL stage. We will refer to a term introduced by this process as a FEP term.
Instead of having many sections with similar names, the author of a FEP MAY combine them.
Instead of defining a new FEP term, the author of a FEP MAY decide to include terms from another namespace as a FEP Term. For example, we decide to introduce conversation as follows
\"fep-xxxx-conversation\": {\n \"@id\": \"http://ostatus.org#conversation\",\n \"@type\": \"@id\"\n },\n This has the advantage that terms, which are currently used but not properly documented, can be given a proper definition. Furthermore, this will help keep the @context block small. When doing so, the author MUST either provide a reference to the current definition of the object OR write a new definition of the object. If a reference is provided, it should be considered best pratice to quote the reference.
In [FEP-61ce], I plan to introduce the term eventSource. As this will most likely be the first FEP using the herein defined procces, this will lead to /feps/assets/fep-2e40/namespace.json being changed to
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"fep\": \"https://www.w3id.org/fep#\",\n \"fep-61ce-eventSource\": {\n \"@id\": \"fep:fep-61ce-eventSource\",\n \"@type\": \"@id\"\n }\n }\n ]\n}\n with usage information
The term eventSource is to be as part of the endpoints of an [ActivityPub] Actor. It specifies an endpoint, where the Client can receive push notifications using the Server Side Events protocol of activities being added to collections on the server. By default the inbox collection of the Actor is used. By specifying the X-ActivityPub-Collection header a different collection can be specified to retrieve push notifications from.
Example usage:
{\n \"@context\": [\"https://www.w3id.org/fep\",\n {\"eventSource\": \"fep-61ce-eventSource\"}\n ],\n \"type\": \"Person\",\n \"id\": \"https://example.com/client_actor\",\n \"inbox\": \"https://example.com/client_actor/inbox\",\n \"outbox\": \"https://example.com/client_actor/outbox\",\n \"preferredUsername\": \"actor\",\n \"endpoints\": {\n \"proxyUrl\": \"https://example.com/client_actor/proxyUrl\",\n \"eventSource\": \"https://example.com/client_actor/eventSource\"\n }\n}\n "},{"location":"fep/2e40/#promotion-to-common-terms","title":"Promotion to common terms","text":"While the above example is already nice looking, it still has the uglyness of needing to define eventSource as fep-61ce/eventSource. We will call the process of adding the line
\"eventSource\": \"fep-61ce-eventSource\"\n to /feps/assets/fep-2e40/namespace.json a promotion to common term.
In order to be elligible for promotion, a term MUST NOT conflict with any term currently in the vocabulary. For example, if the term visualization was already introduced in a previous FEP, it is not acceptable to introduce visualisation and expect promotion of this term.
Any term elligible for promotion CAN be promoted to a common term by the author of the FEP after the FEP has been declared FINAL. If an author does not seek to promotion of a term, it SHOULD be noted in the section introducing this term.
"},{"location":"fep/2e40/#secondary-fep-vocabularies","title":"Secondary FEP Vocabularies","text":"We have now introduced a process for the common or primary vocabulary with context in /feps/assets/fep-2e40/namespace.json and documentation in /feps/assets/fep-2e40/namespace.json. We will now introduce secondary vocabularies. These are meant to be an option to introduce many domain specific terms without having to add them to the primary vocabulary.
An author of a FEP MAY include a section titled \"Introducing Secondary FEP Vocabulary fep-DOMAIN\". Here DOMAIN is a short name representing the domain. Then files /feps/fep-xxxx/namespace.json and /feps/fep-xxxx/namespace.md can be created. An example of a file of the @context file is given below. By including the original fep context, one can keep duplication to a minimum. Similarly, the documentation file should only explain the namespace and which additional terms are promoted or overridden. For the documentation of the fep terms, one should refer back to the original documentation.
Furthermore, the author MAY adjust the configuration of [W3-ID] to reflect his changes. All obligations that are MAY during the draft stage will turn into MUST before the FEP can be finalized.
"},{"location":"fep/2e40/#example-the-barber-vocabulary","title":"Example: The barber vocabulary","text":"The barber community in the FediVerse has decided that, they need a marker if people shave themself. So they have introduce FEP-ba1b and defined the term shavesHimself. It was decided not to promote this term to the primary vocabulary. Instead the barber community has decided to introduce FEP-ba2b, in which they introduce the secondary FEP vocabulary fep-barber. The context of this vocabulary then takes the form:
{\n \"@context\": [\n \"https://www.w3id.org/fep\",\n {\n \"shavesHimself\": \"fep-ba1b-shavesHimself\"\n }\n ]\n}\n By including the primary FEP vocabulary, it is ensured that changes to the primary vocabulary carry over to the secondary fep-barber vocabulary. Furthermore, by the promotion of shavesHimself taking place after the inclusion of the primary vocabulary, it is ensure that the primary vocabulary cannot override it.
The comments by aschrijver and trwnh on SocialHub have helped me greatly in clarifying and formulating the concepts discussed in this FEP.
"},{"location":"fep/2e40/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/2e40/namespace/","title":"The FEP Vocabulary","text":"This file contains the description of Terms of the FEP Vocabulary. The FEP Vocabulary is defined according to [FEP-2e40]. The FEP Vocabulary is an extension of Activity Streams 2.0, which is described in [AS-NS].
"},{"location":"fep/2e40/namespace/#promoted-terms","title":"Promoted terms","text":"The following terms have been elevated to be usable without their fep prefix.
"},{"location":"fep/2e40/namespace/#definitions-of-terms","title":"Definitions of Terms","text":"This section contains the definition of terms separated by FEP.
"},{"location":"fep/2e40/namespace/#references","title":"References","text":"DRAFT 2024-01-31 #255 - codeberg"},{"location":"fep/3264/#summary","title":"Summary","text":"This document describes an implementation of project planning and work coordination based on ActivityPub protocol and Valueflows vocabulary. It includes planning what people want to do, and (optionally) recording what is done.
"},{"location":"fep/3264/#history","title":"History","text":"This is a parallel submission to FEP-c5a1 To-do's, which supports much simpler planning. Both planning submissions continue the idea of Valueflows extensions to ActivityPub/ActivityStreams based on use case, started by silverpill with FEP-0837 Federated Marketplace.
"},{"location":"fep/3264/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/3264/#planning-a-project-or-some-work","title":"Planning a project or some work","text":"This FEP will support work plans or projects of any size, using a Plan with one or more Processes, each with one or more input and/or output Commitments.
Consuming implementations which don't have planning features MAY display plan-related objects similarly to Note objects.
In all cases, the objects can also be discussed using Note objects and replyTo.
Valueflows defines a Plan as a logical collection of processes that constitute a body of scheduled work with defined deliverable(s).
The representation of a plan is a JSON document with the following properties:
id (REQUIRED): the plan's unique global identifier.type (REQUIRED): the type of the object SHOULD be Plan. If interoperability with other ActivityPub services is desirable, implementers MAY also use object types from Activity Vocabulary, such as Note.attributedTo (REQUIRED): the actor who published the plan.name (REQUIRED): the title of the plan.content (OPTIONAL): the description of the plan. The type of content SHOULD be text/html.published (RECOMMENDED): the date and time at which the plan was published.to (REQUIRED): the audience of the plan.planIncludes (REQUIRED): the processes that are part of this plan (see below).A Process is defined as An activity that changes inputs into outputs, by transforming or transporting economic resource(s). Processes that are part of a plan can be part of a series (graph) of processes that produce the final output(s) at the end; and/or they can be unrelated to each other, and produce separate final output(s) for the plan.
The representation of a process is a JSON document with the following properties:
id (REQUIRED): the process's unique global identifier.type (REQUIRED): the type of the object MUST be Process.name (REQUIRED): an informal or formal textual identifier for a process.hasBeginning (OPTIONAL): the date and time the process is planned to begin.hasEnd (OPTIONAL): the date and time the process is planned to end.content (OPTIONAL): the description of what is to be done in the process. The type of content SHOULD be text/html.hasInput (OPTIONAL): the input commitment(s) of the process (see below).hasOutput (OPTIONAL): the output commitment(s) of the process (see below).A Commitment is defined as A planned economic flow that has been promised by an agent to another agent.
id (REQUIRED): the commitment's unique global identifier.type (REQUIRED): the type of the object MUST be Commitment.action (REQUIRED): the type of economic transaction. If the commitment is an input, the value of this property SHOULD be one of: work, use, consume, cite, pickup, accept. It the commitment is an output, the value of this property SHOULD be one of: produce, deliverService, dropoff, modify. See the action definitions and behaviors for more information.resourceConformsTo (RECOMMENDED): the type of an economic resource (can be a skill or type of work for the work action). Could be any URI.resourceQuantity (OPTIONAL): the amount and unit of the non-effort-based economic resource. Either resourceQuantity or effortQuantity is RECOMMENDED, and both can be included in case of use. This is an object with two properties:hasUnit (REQUIRED): name of the unit, according to Ontology of units of Measure classification.hasNumericalValue (REQUIRED): amount of the resource.effortQuantity (OPTIONAL): the amount and unit of the work or use effort-based action. This is an object with two properties:hasUnit (REQUIRED): name of the unit, according to Ontology of units of Measure classification.hasNumericalValue (REQUIRED): amount of the effort.provider (RECOMMENDED): the actor who commits to providing the resource. If not included, it is assumed to be the attributedTo actor.receiver (RECOMMENDED): the actor who commits to receiving the resource. If not included, it is assumed to be the to actor.content (OPTIONAL): the description of what is to be done for this commitment. The type of content SHOULD be text/html.Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Plan\": \"vf:Plan\",\n \"Process\": \"vf:Process\",\n \"Commitment\": \"vf:Commitment\",\n \"receiver\": \"vf:receiver\",\n \"provider\": \"vf:provider\",\n \"action\": \"vf:action\",\n \"resourceConformsTo\": \"vf:resourceConformsTo\",\n \"resourceQuantity\": \"vf:resourceQuantity\",\n \"effortQuantity\": \"vf:effortQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\",\n \"planIncludes\": \"vf:planIncludes\",\n \"hasBeginning\": \"vf:hasBeginning\",\n \"hasEnd\": \"vf:hasEnd\",\n \"hasInput\": \"vf:hasInput\",\n \"hasOutput\": \"vf:hasOutput\"\n }\n ],\n \"type\": \"Plan\",\n \"id\": \"https://project.example/plans/ddde9d6f-6f3b-4770-a966-3a18ef006931\",\n \"attributedTo\": \"https://project.example/actors/alice\",\n \"name\": \"Making Apple Cider\",\n \"content\": \"Annual weekend of community apple cider making\",\n \"published\": \"2024-09-18T19:22:03.918737Z\",\n \"to\": \"https://project.example/actors/gleaning-collective\",\n \"planIncludes\": [\n {\n \"type\": \"Process\",\n \"id\": \"https://project.example/processes/ddde9d6f-6f3b-4770-a966-3a18ef006931#pick\",\n \"name\": \"Pick Apples\",\n \"hasBeginning\": \"2024-10-18T09:00:00.000000Z\",\n \"hasEnd\": \"2024-10-18T18:00:00.000000Z\",\n \"content\": \"Meet at the orchard, bring your own basket and a lunch, and be ready for some outdoor fall work and fun.\",\n \"hasInput\": [\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#pick1\",\n \"action\": \"work\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q112729805\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"6\"\n },\n \"provider\": \"https://project.example/actors/bob\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\"\n },\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#pick2\",\n \"action\": \"work\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q112729805\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"6\"\n },\n \"provider\": \"https://project.example/actors/chiam\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\"\n },\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#pick3\",\n \"action\": \"work\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q112729805\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"6\"\n },\n \"provider\": \"https://project.example/actors/alice\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\"\n }\n ],\n \"hasOutput\": [\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/outputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#pick4\",\n \"action\": \"produce\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q89\",\n \"resourceQuantity\": {\n \"hasUnit\": \"pound\",\n \"hasNumericalValue\": \"500\"\n },\n \"provider\": \"https://project.example/actors/gleaning-collective\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\"\n }\n ]\n },\n {\n \"type\": \"Process\",\n \"id\": \"https://project.example/processes/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider\",\n \"name\": \"Make Cider\",\n \"hasBeginning\": \"2024-10-19T09:00:00.000000Z\",\n \"hasEnd\": \"2024-10-19T16:00:00.000000Z\",\n \"content\": \"Meet at the community building, be prepared to get messy. We will press the apples, then clean the press.\",\n \"hasInput\": [\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider1\",\n \"action\": \"consume\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q89\",\n \"resourceQuantity\": {\n \"hasUnit\": \"pound\",\n \"hasNumericalValue\": \"500\"\n },\n \"provider\": \"https://project.example/actors/gleaning-collective\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\",\n \"content\": \"The apples picked the day before.\"\n },\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider2\",\n \"action\": \"use\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q674484\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"7\"\n },\n \"provider\": \"https://project.example/actors/diego\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\",\n \"content\": \"Diego will drop his cider press off early in the morning, and pick it up later, preferably nice and clean.\"\n },\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider3\",\n \"action\": \"work\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q3485549\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"4\"\n },\n \"provider\": \"https://project.example/actors/chiam\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\"\n },\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider4\",\n \"action\": \"work\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q3485549\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"6\"\n },\n \"provider\": \"https://project.example/actors/bob\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\"\n }\n ],\n \"hasOutput\": [\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/outputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider5\",\n \"action\": \"produce\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q5977438\",\n \"resourceQuantity\": {\n \"hasUnit\": \"gallon-US\",\n \"hasNumericalValue\": \"15\"\n },\n \"provider\": \"https://project.example/actors/gleaning-collective\",\n \"receiver\": \"https://project.example/actors/food-pantry\"\n },\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/outputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider6\",\n \"action\": \"produce\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q2836947\",\n \"resourceQuantity\": {\n \"hasUnit\": \"pound\",\n \"hasNumericalValue\": \"100\"\n },\n \"provider\": \"https://project.example/actors/gleaning-collective\",\n \"receiver\": \"https://project.example/actors/singing-trees-farm\",\n \"content\": \"The apple remains will be great pig food, leave it in the big bin, and they will pick it up.\"\n }\n ]\n }\n ]\n}\n"},{"location":"fep/3264/#accepting-a-commitment","title":"Accepting a Commitment","text":"The object of Accept activity MUST be the id of the Commitment in the Plan object previously sent to the actor.
Activity MAY contain content property for further coordination.
Example:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Accept\",\n \"id\": \"https://project.example/activities/059f08fa-31b1-4136-8d76-5987d705a0ac\",\n \"actor\": \"https://project.example/actors/bob\",\n \"object\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider4\",\n \"content\": \"I'll come early to help unload the cider press.\",\n \"to\": \"https://project.example/actors/alice\"\n}\n"},{"location":"fep/3264/#rejecting-a-commitment","title":"Rejecting a Commitment","text":"The object of Reject activity MUST be the id of the Commitment in the Plan object previously sent to the actor.
Activity MAY contain content property indicating the reason for rejection.
Example:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Reject\",\n \"id\": \"https://project.example/activities/8c05f97f-1531-4b70-9ca8-4ee4a09f36a4\",\n \"actor\": \"https://project.example/actors/chiam\",\n \"object\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider3\",\n \"content\": \"Sorry, already working elsewhere that day, will check with Eleanore.\",\n \"to\": \"https://project.example/actors/alice\"\n}\n"},{"location":"fep/3264/#adding-a-commitment-to-the-plan","title":"Adding a Commitment to the Plan","text":"In some cases, an actor might want to add a new commitment to the plan.
The representation of a commitment is a JSON document with the following properties:
id (REQUIRED): the commitment's unique global identifier.type (REQUIRED): the type of the object SHOULD be Commitment. If interoperability with other ActivityPub services is desirable, implementers MAY also use object types from Activity Vocabulary, such as Note.attributedTo (REQUIRED): the actor who published the commitment.content (OPTIONAL): the description of the commitment or communication about the commitment. The type of content SHOULD be text/html.published (RECOMMENDED): the date and time at which the commitment was published.to (REQUIRED): the audience of the commitment.action (REQUIRED): the type of economic transaction. If the commitment is an input, the value of this property SHOULD be one of: work, use, consume, cite, pickup, accept. It the commitment is an output, the value of this property SHOULD be one of: produce, deliverService, dropoff, modify. See the action definitions and behaviors for more information.inputOf: the process for which this commitment is an input. Either inputOf or outputOf is REQUIRED.outputOf: the process for which this commitment is an output. Either inputOf or outputOf is REQUIRED.context (OPTIONAL): the plan the commitment is part of.resourceConformsTo (RECOMMENDED): the type of an economic resource (can be a skill or type of work for the work action). Could be any URI.resourceQuantity: the amount and unit of the non-effort-based economic resource. Either resourceQuantity or effortQuantity is RECOMMENDED, and both can be included in case of use. This is an object with two properties:hasUnit (REQUIRED): name of the unit, according to Ontology of units of Measure classification.hasNumericalValue (REQUIRED): amount of the resource.effortQuantity: the amount and unit of the work or use effort-based action. Either resourceQuantity or effortQuantity is RECOMMENDED, and both can be included in case of use. This is an object with two properties:hasUnit (REQUIRED): name of the unit, according to Ontology of units of Measure classification.hasNumericalValue (REQUIRED): amount of the resource.provider (RECOMMENDED): the actor who commits to providing the resource. If not included, it is assumed to be the attributedTo actor.receiver (RECOMMENDED): the actor who commits to receiving the resource. If not included, it is assumed to be the to actor.{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Commitment\": \"vf:Commitment\",\n \"inputOf\": \"vf:inputOf\",\n \"receiver\": \"vf:receiver\",\n \"provider\": \"vf:provider\",\n \"action\": \"vf:action\",\n \"resourceConformsTo\": \"vf:resourceConformsTo\",\n \"effortQuantity\": \"vf:effortQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\"\n }\n ],\n \"type\": \"Create\",\n \"id\": \"https://project.example/activities/ad2f7ee1-6567-413e-a10b-72650cbdc743/create\",\n \"actor\": \"https://project.example/actors/eleanore\",\n \"object\": {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ad2f7ee1-6567-413e-a10b-72650cbdc743\",\n \"attributedTo\": \"https://project.example/actors/eleanore\",\n \"context\": \"https://project.example/plans/ddde9d6f-6f3b-4770-a966-3a18ef006931\",\n \"published\": \"2024-09-21T14:13:41.843794Z\",\n \"inputOf\": \"https://project.example/processes/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider\",\n \"action\": \"work\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q3485549\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"5\"\n },\n \"provider\": \"https://project.example/actors/eleanore\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\"\n },\n \"to\": \"https://project.example/actors/alice\"\n}\n"},{"location":"fep/3264/#recording-what-actually-was-done","title":"Recording what actually was done","text":"Actual economic activity is represented with EconomicEvents in Valueflows. Economic events can be recorded as fulfilling a planned commitment, or by themselves if they were not explicitly planned. In either case, in the scope of this FEP, they are also inputs or outputs of a process.
This FEP does not include creating and updating of actual resource inventory, which is also effected through economic events.
The representation of an economic event is a JSON document with the following properties:
id (REQUIRED): the economic event's unique global identifier.type (REQUIRED): the type of the object SHOULD be EconomicEvent. If interoperability with other ActivityPub services is desirable, implementers MAY also use object types from Activity Vocabulary, such as Note.attributedTo (REQUIRED): the actor who published the economic event.content (OPTIONAL): the description of the economic event or communication about the economic event. The type of content SHOULD be text/html.published (RECOMMENDED): the date and time at which the economic event was published.to (REQUIRED): the audience of the economic event.action (REQUIRED): the type of economic transaction. If the economic event is an input, the value of this property SHOULD be one of: work, use, consume, cite, pickup, accept. It the economic event is an output, the value of this property SHOULD be one of: produce, deliverService, dropoff, modify. See the action definitions and behaviors for more information.fulfills: the commitment the economic event is completely or partially fulfilling, REQUIRED if there is sucn a commitment, otherwise N/A.inputOf: the process for which this economic event is an input. Either inputOf or outputOf is REQUIRED.outputOf: the process for which this commitment is an output. Either inputOf or outputOf is REQUIRED.context (OPTIONAL): the plan the economic event is part of.resourceConformsTo (RECOMMENDED): the type of an economic resource (can be a skill or type of work for the work action). Could be any URI.resourceQuantity: the amount and unit of the non-effort-based economic resource. Either resourceQuantity or effortQuantity is RECOMMENDED, and both can be included in case of use. This is an object with two properties:hasUnit (REQUIRED): name of the unit, according to Ontology of units of Measure classification.hasNumericalValue (REQUIRED): amount of the resource.effortQuantity: the amount and unit of the work or use effort-based action. Either resourceQuantity or effortQuantity is RECOMMENDED, and both can be included in case of use. This is an object with two properties:hasUnit (REQUIRED): name of the unit, according to Ontology of units of Measure classification.hasNumericalValue (REQUIRED): amount of the resource.provider (RECOMMENDED): the actor who provided the resource. If not included, it is assumed to be the attributedTo actor.receiver (RECOMMENDED): the actor who received the resource. If not included, it is assumed to be the to actor.finished (OPTIONAL): set to true if this economic event completes the commitment. (Note more than one economic event can be recorded against the same commitment.){\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"EconomicEvent\": \"vf:EconomicEvent\",\n \"inputOf\": \"vf:inputOf\",\n \"receiver\": \"vf:receiver\",\n \"provider\": \"vf:provider\",\n \"action\": \"vf:action\",\n \"resourceConformsTo\": \"vf:resourceConformsTo\",\n \"effortQuantity\": \"vf:effortQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\",\n \"finished\": \"vf:finished\"\n }\n ],\n \"type\": \"Create\",\n \"id\": \"https://project.example/activities/ad2f7ee1-6567-413e-a10b-72650cbdc932/create\",\n \"actor\": \"https://project.example/actors/bob\",\n \"object\": {\n \"type\": \"EconomicEvent\",\n \"id\": \"https://project.example/inputs/ad2f7ee1-6567-413e-a10b-72650cbdc932\",\n \"attributedTo\": \"https://project.example/actors/bob\",\n \"context\": \"https://project.example/plans/ddde9d6f-6f3b-4770-a966-3a18ef006931\",\n \"published\": \"2024-10-21T14:16:41.843794Z\",\n \"fulfills\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider4\",\n \"inputOf\": \"https://project.example/processes/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider\",\n \"action\": \"work\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q3485549\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"6.5\"\n },\n \"provider\": \"https://project.example/actors/bob\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\",\n \"finished\": true\n },\n \"to\": \"https://project.example/actors/alice\"\n}\n"},{"location":"fep/3264/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/37f2/","title":"Index","text":"hide: - navigation description: \"A FEP proposing that W3C Social Web Incubator Community Group harmonize its process with other W3C Groups as well as the Fediverse Enhancement Process on socialhub.activitypub.rocks by * posting Calls for Consensus on the SWICG mailing list public-swicg@w3.org * engaging other SWICG fora like socialhub.activitypub.rocks (linked to as \"Forum\" from the SWICG Webpage) * having a shared response period\"
"},{"location":"fep/37f2/#fep-37f2-a-policy-for-calls-for-consensus-on-swicg-group-decisions","title":"FEP-37f2: a policy for calls for consensus on SWICG group decisions","text":"Authors Status Date received Tracking issue Discussions Repository bengo https://mastodon.social/@bengoDRAFT 2023-09-28 #184 - codeberg"},{"location":"fep/37f2/#summary","title":"Summary","text":"A FEP proposing that W3C Social Web Incubator Community Group harmonize its process with other W3C Groups as well as the Fediverse Enhancement Process on socialhub.activitypub.rocks by: * posting Calls for Consensus on the SWICG mailing list public-swicg@w3.org * engaging other SWICG fora like socialhub.activitypub.rocks (linked to as \"Forum\" from the SWICG Webpage) * having a shared response period
"},{"location":"fep/37f2/#introduction","title":"Introduction","text":"The Social Web Incubation Community Group is missing an explicit decision-making policy, which essentially all other W3C community groups have to ensure asynchronous and healthy consensus mechanisms across timezones and participatory modes.
"},{"location":"fep/37f2/#proposal","title":"Proposal","text":"W3C SWICG will seek to make decisions through consensus and due process, per the W3C Process Document, \u00a75.2.1 Consensus.
To afford asynchronous decisions and organizational deliberation, any resolution (including publication decisions) taken in a face-to-face meeting or teleconference will be considered provisional.
A call for consensus (CFC) will be issued for all resolutions via email to public-swicg@w3.org (archives). The presence of formal resolutions will be indicated by a \"CFC\" prefix in the subject line of the email. Additional outreach to community venues for more affirmative consent is strongly encouraged. There will be a response period of 14 days. If no sustained objections are raised by the end of the response period, the resolution will be considered to have consensus as a resolution of the Community Group, i.e. a group decision.
All decisions made by the group should be considered resolved unless and until new information becomes available or unless reopened at the discretion of the Chairs or the Director.
This policy is an operational agreement per the W3C Community and Business Group Process.
"},{"location":"fep/37f2/#context","title":"Context","text":""},{"location":"fep/37f2/#w3c-groups-with-similar-decision-policies","title":"W3C Groups with Similar Decision Policies","text":"These community groups and working groups have similar decision policies with tentative meeting resolutions and confirmation of calls for consensus via email:
Proposal processes on SWICG Forum with identical response period:
W3C SWICG is a W3C Community Group (CG).
CGs are described in their process document as follows (excerpted for concision):
This document defines W3C Community Groups, where anyone may develop Specifications, hold discussions, develop tests, and so on, with no participation fee. \u2026
Community Groups that develop specifications do so under policies designed to strike a balance between ease of participation and safety for implementers and patent holders \u2026
A Community Group may adopt operational agreements\u2026 that establish the group\u2019s scope of work, decision-making processes, communications preferences, and other operations. \u2026
The following rules govern Community Group operational agreements: * They must be publicly documented. * They must be fair and must not unreasonably favor or discriminate against any group participant or their employer. * They must not conflict with or modify this Community and Business Group Process, the Community Contributor License Agreement (CLA), or the Final Specification Agreement. \u2026
the Chair determines the means by which the group adopts and modifies operational agreements. The Chair must give actual notice to the participants of any material changes to the agreements. Participants may resign from the group if they do not wish to participate under the new agreements. \u2026
Note: W3C encourages groups adopt decision-making policies that promote consensus. \u2026
Each Community Group must have at least one Chair who is responsible for ensuring the group fulfills the requirements of this document as well as the group\u2019s operational agreements.
"},{"location":"fep/37f2/#related-reading","title":"Related Reading","text":"The title of this proposal was generated in line with norms established by Content addressed vocabulary for extensions and FEP-a4ed: The Fediverse Enhancement Proposal Process.
\u26a1 P='a policy for calls for consensus on SWICG group decisions'\n\u26a1 echo \"SWIP-$(echo -n \"$P\" | sha256sum | cut -c-4): $P\"\nSWIP-37f2: a policy for calls for consensus on group decisions\n The 'SW' in 'SWIP' stands for 'Social Web'.
This proposal was initially published at: * https://socialweb.coop/SWIP/37f2/a-policy-for-calls-for-consensus-on-swicg-group-decisions/
"},{"location":"fep/37f2/#copyright","title":"Copyright","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/3b86/","title":"Index","text":"hide: - navigation description: \"\"Activity Intents\" extend the capabilities of an ActivityPub server beyond a user's outbox, and enable direct interactions with content on the wider social web. They do this by publishing a machine-readable list of public URLs where users can perform key activities (such as Follow, Like, or Announce) allowing other websites to initiate remote social interactions without cumbersome copying/pasting of URL strings.\"
"},{"location":"fep/3b86/#fep-3b86-activity-intents","title":"FEP-3b86: Activity Intents","text":"Authors Status Date received Tracking issue Discussions Repository Ben Pate \\<@benpate@mastodon.social>DRAFT 2024-04-19 #302 - codeberg"},{"location":"fep/3b86/#summary","title":"Summary","text":"\"Activity Intents\" extend the capabilities of an ActivityPub server beyond a user's outbox, and enable direct interactions with content on the wider social web. They do this by publishing a machine-readable list of public URLs where users can perform key activities (such as Follow, Like, or Announce) allowing other websites to initiate remote social interactions without cumbersome copying/pasting of URL strings.
The key words \"MUST\", \"SHOULD\", and \"MAY\" are to be interpreted as described in RFC2119.
For the purposes of this document, a \"Home Server\" is the location where a user is logged in, and that publishes the Actor's Activity Intents via WebFinger. A \"Remote Server\" is another location on the Internet that the user wants to interact with, and that will request/receive those intents via WebFinger.
"},{"location":"fep/3b86/#2-history","title":"2. History","text":"Most centralized social media services have widgets that allow users on the wider Internet to interact with their social services. These include \"like\" and \"share\" buttons that third-party websites embed into their content, and link users back to their corresponding social media account.
This is difficult to achieve in a federated environment.
There have been other attempts to make a \"Share on Mastodon\" button that performs a similar action on the Fediverse. But these tasks are implemented differently by different Fediverse servers. For example, Mastodon uses /share, while Hubzilla uses /rpost. Parameters to each application are often different, using variations of text, title, url, and other values.
The lack of a unified standard has led developers to hard-code endpoints for each distinct application. This is brittle and vulnerable to changes by server authors. It also pins those URL endpoints, making them difficult for servers to change in the future without breaking an unknown number of \"share\" buttons out in the wild.
What is needed is a systematic way for each server to announce the endpoint URLs that they support.
"},{"location":"fep/3b86/#3-activity-intents","title":"3. Activity Intents","text":"In the most basic terms, Activity Intents expand on the common Fediverse use of WebFinger in FEP-4adb to include mappings between any Activity Type and the URL endpoint where that user can perform it. This expands and standardizes the \"remote follow\" workflow that was used by oStatus protocol, but has not been fully implemented by newer Fediverse applications and no longer has a public specification document.
When generating a WebFinger result for a user account, servers supporting Activity Intents SHOULD respond with one or more intent links in the \"links\" property. Activity Intent links MUST have rel and template properties. All others properties are ignored.
While the number and specific behavior of each Activity Intent is defined independently by each home server, to maximize compatibility home servers SHOULD publish all of the activities that they support. This gives remote servers the greatest opportunity to match as many activities as possible.
"},{"location":"fep/3b86/#31-example","title":"3.1. Example","text":"Here is an example response from a WebFinger server which includes three Activity Intents appended to the end of its links property.
{\n \"subject\": \"acct:benpate@mastodon.social\",\n \"aliases\": [\n \"https://mastodon.social/@benpate\",\n \"https://mastodon.social/users/benpate\"\n ],\n \"links\": [\n {\n \"rel\": \"http://webfinger.net/rel/profile-page\",\n \"type\": \"text/html\",\n \"href\": \"https://mastodon.social/@benpate\"\n },\n {\n \"rel\": \"self\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://mastodon.social/users/benpate\"\n },\n {\n \"rel\": \"http://ostatus.org/schema/1.0/subscribe\",\n \"template\": \"https://mastodon.social/authorize_interaction?uri={uri}\"\n },\n {\n \"rel\": \"https://w3id.org/fep/3b86/Follow\",\n \"template\": \"https://mastodon.social/authorize_interaction?uri={object}\"\n },\n {\n \"rel\": \"https://w3id.org/fep/3b86/Create\",\n \"template\": \"https://mastodon.social/share?uri={object}\"\n },\n {\n \"rel\": \"https://w3id.org/fep/3b86/Like\",\n \"template\": \"https://mastodon.social/intents/like?id={object}\"\n }\n ]\n}\n"},{"location":"fep/3b86/#32-required-link-properties","title":"3.2. Required Link Properties","text":"rel: Activity Intents use the https://w3id.org/fep/3b86/* (as described in FEP-888d) to designate the kind of activity intent, where * represents the particular Activity the user intends to perform. These relations -- such as https://w3id.org/fep/3b86/Follow, and https://w3id.org/fep/3b86/Create -- are listed in detail below.
template: Links use URI Templates as used in RFC-6415 to designate URLS and parameters to be injected by the caller -- as in {uri} or {name} values. Note: WebFinger does not specify the template parameter, but it is used here because 1) there's a semantic benefit in identifying values not a links, but as link templates, and 2) it is already in use by oStatus, which should make it familiar to Fediverse developers.
Parameter names are chosen to correspond with Activity Vocabulary properties and may differ from parameters used by pre-existing implementations.
To prevent unrecognized properties from corrupting a workflow: * Remote servers MUST be able to replace all recognized values with the appropriate string. * Remote servers MUST replace unrecognized values with an empty string.
"},{"location":"fep/3b86/#33-template-parameters","title":"3.3 Template Parameters","text":"In all cases, Activity Intents intentionally use the property names defined in the W3C standard Activity Vocabulary. However, intents must fit into a URL, and must use commonly understood fields. So, Intent parameters can only use IDs -- URL references to JSON-LD resources available elsewhere on the Internet.
In addition, remote servers MUST Percent Encode all values replaced in the URI Template.
"},{"location":"fep/3b86/#34-workflow-actions","title":"3.4 Workflow Actions","text":"Activity Intents MAY include additional query parameters on-success and on-cancel that allow home servers to return users to their original workflow on the remote server:
on-success - If present in the link template, this value identifies the action that the home server SHOULD take once the Activity Intent workflow is complete. If this parameter is missing, then the resulting page is left up to the home server to choose.
on-cancel - If present in the link template, this value identifies the action that the home server SHOULD take if they abort the Activity Intent workflow. If this parameter is missing, then the resulting page is left up to the home server to choose.
"},{"location":"fep/3b86/#action-close","title":"Action: (close)","text":"If the value of either on-success or on-cancel parameter is the string (close) then the home server can assume that it is running in a pop-up window, and SHOULD close the current window.
If the value of either on-success or on-cancel query parameter is a valid URL, then the home server SHOULD redirect clients to the provided URL. When redirecting to a new URL, the home server MUST use an interstitial page that notified the user that they are being redirected, and displays the URL of the new page to them.
IMPORTANT! Unvalidated Redirects are a documented vulnerability that opens the home server up to phishing attacks.
To avoid a potential security vulnerability when handling on-success and on-cancel workflows, it is important that the home server: \"Force all redirects to first go through a page notifying users that they are going off of your site, with the destination clearly displayed, and have them click a link to confirm.\" (owasp.org)
The user's home server is a trusted environment that manages the user's sign-in status along with the rest of their social inbox and outbox. When the remote server links to an Activity Intent provided by the home server, the layout, fields, and UI are all determined by the home server
Remote servers MAY open Activity Intent links in many different environments, such as: 1. a full browser window 2. a small pop-up window 3. a mobile app HTML view 4. or other constrained environments.
Home servers SHOULD build their UI with minimal assumptions so that the endpoint will fit well into as many environments as possible. Home servers may not be aware if the remote server opens the Activity Intent link in a separate popup window or not, so remote servers SHOULD include on-success and on-cancel parameters that redirect back to its own pages so that it can close the pop-up itself.
This intent corresponds to the ActivityStreams Accept activity and is defined using the link relation https://w3id.org/fep/3b86/Accept.
The Accept intent publishes the API endpoint where the current user can \"accept\" the designated object.
"},{"location":"fep/3b86/#411-parameters","title":"4.1.1. Parameters","text":"{object} - ID of the object that the user will accept when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform they abort the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Accept\",\n \"template\": \"https://server.org/intents/accept?objectId={object}\"\n}\n"},{"location":"fep/3b86/#42-add-intent","title":"4.2. Add Intent","text":"This intent corresponds to the ActivityStreams Add activity and is defined using the link relation https://w3id.org/fep/3b86/Add .
The Add intent publishes the API endpoint where the current user can add an object to the designated collection..
"},{"location":"fep/3b86/#421-parameters","title":"4.2.1. Parameters","text":"{object} ID of the object that the user will add when they use this workflow.{target} ID of the collection being added to.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Add\",\n \"template\": \"https://server.org/intents/add?objectId={object}&targetId={target}\"\n}\n"},{"location":"fep/3b86/#43-announce-intent","title":"4.3. Announce Intent","text":"This intent corresponds to the ActivityStreams Announce activity and is defined using the link relation https://w3id.org/fep/3b86/Announce.
The Announce intent publishes the API endpoint where the current user can announce, or \"boost\" the provided document in their home server.
"},{"location":"fep/3b86/#431-parameters","title":"4.3.1. Parameters","text":"{object} - ID of the document that the user will boost when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Announce\",\n \"template\": \"https://server.org/intents/announce?objectId={object}\"\n}\n"},{"location":"fep/3b86/#44-arrive-intent","title":"4.4. Arrive Intent","text":"This intent corresponds to the ActivityStreams Arrive activity and is defined using the link relation https://w3id.org/fep/3b86/Arrive.
The Arrive intent publishes the API endpoint where the current user can indicate that they have arrived at a particular location from their home server.
"},{"location":"fep/3b86/#441-parameters","title":"4.4.1. Parameters","text":"{location} - ID of the location object where the user will mark as \"arrived\" when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Arrive\",\n \"template\": \"https://server.org/intents/arrive?hotelId={location}\"\n}\n"},{"location":"fep/3b86/#45-block-intent","title":"4.5. Block Intent","text":"This intent corresponds to the ActivityStreams Block activity and is defined using the link relation https://w3id.org/fep/3b86/Block.
The Block intent publishes the API endpoint where the current user can block the provided object from their home server.
"},{"location":"fep/3b86/#451-parameters","title":"4.5.1. Parameters","text":"{object} - ID of the object (document, user, etc) that the user will block when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Block\",\n \"template\": \"https://server.org/intents/block?userId={object}\"\n}\n"},{"location":"fep/3b86/#46-create-intent","title":"4.6. Create Intent","text":"This intent corresponds to the ActivityStreams Create activity and is defined using the link relation https://w3id.org/fep/3b86/Create.
The Create intent publishes the API endpoint where the current user can create a new post in their own outbox.
This is similar to the existing \"share\" endpoints supported by several Fediverse apps, where the user can create a new post in their inbox starting with some pre-populated content.
This Intent differs slightly from others in that it does not take an object ID as a parameter because it expects a the user's home server to create a new object. Instead, the parameters for this Intent are meant to pre-populate into the new object that the user will create.
"},{"location":"fep/3b86/#461-parameters","title":"4.6.1. Parameters","text":"{type} - (optional) Type of object to create (Note, Article, etc). Home servers can determine whether to use or ignore this value.{name} - (optional) Name to pre-populate into the created object.{summary} - (optional) Summary to pre-populate into the created object.{content} - (optional) Text content to pre-populate into the created object.{inReplyTo} - (optional)The ID of the ActivityStreams Document that the user is replying to.{attachment} - (optional) ID of an object (such as a link or an image) that should be included in the created object.{tag} - (optional) ID of an object (such as a tag definition) that should be referenced by the created object{startTime} - (optional) a date-time that identifies the actual or expected starting time of the created object. RFC3339 format {endTime} - (optional) a date-time that identifies the actual or expected ending time of the created object. RFC3339 format{describes} (optional) the ID of an object to be described, when creating a Profile object.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Create\",\n \"template\": \"https://mastodon.social/share?text={content}\"\n}\n"},{"location":"fep/3b86/#47-delete-intent","title":"4.7. Delete Intent","text":"This intent corresponds to the ActivityStreams Delete activity and is defined using the link relation https://w3id.org/fep/3b86/Delete.
The Delete intent publishes the API endpoint where the current user can initiate a \"delete\" request.
"},{"location":"fep/3b86/#471-parameters","title":"4.7.1. Parameters","text":"{object} - ID of the object that the user will delete when they use this workflow.{origin}- (optional) ID of the collection or context that the object will be deleted from.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Delete\",\n \"template\": \"https://server.org/intents/Delete?objectId={object}\"\n}\n"},{"location":"fep/3b86/#48-dislike-intent","title":"4.8. Dislike Intent","text":"This intent corresponds to the ActivityStreams Dislike activity and is defined using the link relation https://w3id.org/fep/3b86/Dislike.
The Dislike intent publishes the API endpoint where the current user can initiate a \"dislike\" request.
"},{"location":"fep/3b86/#481-parameters","title":"4.8.1. Parameters","text":"{object} - ID of the document that the user will dislike when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Dislike\",\n \"template\": \"https://server.org/intent/dislike?objectId={object}\"\n}\n"},{"location":"fep/3b86/#49-flag-intent","title":"4.9. Flag Intent","text":"This intent corresponds to the ActivityStreams Flag activity and is defined using the link relation https://w3id.org/fep/3b86/Flag.
The Flag intent publishes the API endpoint where the current user can initiate a \"flag\" request, which is used to report inappropriate content.
"},{"location":"fep/3b86/#491-parameters","title":"4.9.1. Parameters","text":"{object} - ID of the object (document, user, etc) that the user will flag when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Flag\",\n \"template\": \"https://server.org/intent/flag?objectId={object}\"\n}\n"},{"location":"fep/3b86/#410-follow-intent","title":"4.10. Follow Intent","text":"This intent corresponds to the ActivityStreams Follow activity and is defined using the link relation https://w3id.org/fep/3b86/Follow.
The Follow intent publishes the API endpoint where the current user can initiate a \"follow\" request. This is similar to the remote follow workflow defined by oStatus that is still supported at various levels by several Fediverse apps, but is no longer formally documented.
"},{"location":"fep/3b86/#4101-parameters","title":"4.10.1. Parameters","text":"{object} - ID of the actor that the user will follow when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Follow\",\n \"template\": \"https://mastodon.social/authorize_interaction?uri={object}\"\n}\n"},{"location":"fep/3b86/#411-ignore-intent","title":"4.11. Ignore Intent","text":"This intent corresponds to the ActivityStreams Ignore activity and is defined using the link relation https://w3id.org/fep/3b86/Ignore.
The Ignore intent publishes the API endpoint where the current user can initiate an \"ignore\" request, which is similar to a Block, and is used to ignore or mute various actors and objects online
"},{"location":"fep/3b86/#4111-parameters","title":"4.11.1 Parameters","text":"{object} - ID of the object that the user will mark \"ignored\" when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Ignore\",\n \"template\": \"https://server.org/intents/ignore?objectId={object}\"\n}\n"},{"location":"fep/3b86/#412-invite-intent","title":"4.12. Invite Intent","text":"This intent corresponds to the ActivityStreams Invite activity and is defined using the link relation https://w3id.org/fep/3b86/Invite.
The Invite intent publishes the API endpoint where the current user can initiate an \"invite\" request, which is similar to an Offer, and is used to extend an invitation for the object to the target
"},{"location":"fep/3b86/#4121-parameters","title":"4.12.1. Parameters","text":"{target} - ID of the actor who will receive the invitation.{object} - ID of the object (event, group, etc) that the actor will be invited to.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Invite\",\n \"template\": \"https://server.org/intents/invite?actorId={object}&eventId={target}\"\n}\n"},{"location":"fep/3b86/#413-join-intent","title":"4.13. Join Intent","text":"This intent corresponds to the ActivityStreams Join activity and is defined using the link relation https://w3id.org/fep/3b86/Join.
The Join intent publishes the API endpoint where the current user can initiate a \"join\" request.
"},{"location":"fep/3b86/#4131-parameters","title":"4.13.1. Parameters","text":"{object} - ID of the object that the user will join when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Join\",\n \"template\": \"https://server.org/intents/join?objectId={object}\"\n}\n"},{"location":"fep/3b86/#414-leave-intent","title":"4.14. Leave Intent","text":"This intent corresponds to the ActivityStreams Leave activity and is defined using the link relation https://w3id.org/fep/3b86/Leave.
The Leave intent publishes the API endpoint where the current user can initiate a \"leave\" request.
"},{"location":"fep/3b86/#4141-parameters","title":"4.14.1. Parameters","text":"{object} - ID of the object that the user will leave when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Leave\",\n \"template\": \"https://server.org/intents/leave?objectId={object}\"\n}\n"},{"location":"fep/3b86/#415-like-intent","title":"4.15. Like Intent","text":"This intent corresponds to the ActivityStreams Like activity and is defined using the link relation https://w3id.org/fep/3b86/Like.
The Like intent publishes the API endpoint where the current user can like the current document.
"},{"location":"fep/3b86/#4151-parameters","title":"4.15.1. Parameters","text":"{object} - ID of the object that the user will mark as \"liked\" when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Like\",\n \"template\": \"https://server.com/intents/like?objectId={object}\"\n}\n"},{"location":"fep/3b86/#416-listen-intent","title":"4.16. Listen Intent","text":"This intent corresponds to the ActivityStreams Listen activity and is defined using the link relation https://w3id.org/fep/3b86/Listen.
The Listen intent publishes the API endpoint where the current user can initiate a \"listen\" request.
"},{"location":"fep/3b86/#4161-parameters","title":"4.16.1. Parameters","text":"{object} - ID of the object that the user will mark as \"listened\" to when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Listen\",\n \"template\": \"https://server.org/intents/listen?objectId={object}\"\n}\n"},{"location":"fep/3b86/#417-move-intent","title":"4.17. Move Intent","text":"This intent corresponds to the ActivityStreams Move activity and is defined using the link relation https://w3id.org/fep/3b86/Move.
The Move intent publishes the API endpoint where the current user can initiate a \"move\" request.
"},{"location":"fep/3b86/#4171-parameters","title":"4.17.1. Parameters","text":"{object} - ID of the object that the user will move when they use this workflow.{target} - ID of the collection that the object will be moved to.{origin} - (optional) ID of the collection that the object will be moved from.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Move\",\n \"template\": \"https://server.org/intents/move?objectId={object}&destId={target}\"\n}\n"},{"location":"fep/3b86/#418-offer-intent","title":"4.18. Offer Intent","text":"This intent corresponds to the ActivityStreams Offer activity and is defined using the link relation https://w3id.org/fep/3b86/Offer.
The Offer intent publishes the API endpoint where the current user can initiate an \"offer\" request.
"},{"location":"fep/3b86/#4181-parameters","title":"4.18.1. Parameters","text":"{object} - ID of the object that the user will offer when they use this workflow.{target} - ID of the actor that will receive the offer.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Offer\",\n \"template\": \"https://server.org/intents/offer?objectId={object}&to={target}\"\n}\n"},{"location":"fep/3b86/#419-question-intent","title":"4.19. Question Intent","text":"This intent corresponds to the ActivityStreams Question activity and is defined using the link relation https://w3id.org/fep/3b86/Question.
The Question intent publishes the API endpoint where the current user can initiate an \"question\" workflow.
"},{"location":"fep/3b86/#4191-parameters","title":"4.19.1. Parameters","text":"{name} - The \"name\" property to pre-populate into the question the user will ask when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Question\",\n \"template\": \"https://server.org/intents/question?name={name}\"\n}\n"},{"location":"fep/3b86/#420-read-intent","title":"4.20. Read Intent","text":"This intent corresponds to the ActivityStreams Read activity and is defined using the link relation https://w3id.org/fep/3b86/Read.
The Read intent publishes the API endpoint where the current user can initiate an \"read\" request.
"},{"location":"fep/3b86/#4201-parameters","title":"4.20.1. Parameters","text":"{object} - ID of the object that the user will mark as \"read\" when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Read\",\n \"template\": \"https://server.org/intents/object?objectId={object}\"\n}\n"},{"location":"fep/3b86/#421-reject-intent","title":"4.21. Reject Intent","text":"This intent corresponds to the ActivityStreams Reject activity and is defined using the link relation https://w3id.org/fep/3b86/Reject.
The Reject intent publishes the API endpoint where the current user can initiate an \"reject\" request.
"},{"location":"fep/3b86/#4211-parameters","title":"4.21.1. Parameters","text":"{object} - ID of the object that the user will reject when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Reject\",\n \"template\": \"https://server.org/intents/reject?offerId={object}\"\n}\n"},{"location":"fep/3b86/#422-remove-intent","title":"4.22. Remove Intent","text":"This intent corresponds to the ActivityStreams Remove activity and is defined using the link relation https://w3id.org/fep/3b86/Remove.
The Remove intent publishes the API endpoint where the current user can initiate a \"remove\" request.
"},{"location":"fep/3b86/#4221-parameters","title":"4.22.1. Parameters","text":"{object} - ID of the object that the user will remove when they use this workflow.{target} - (optional) The ID of the collection that the object will be removed from.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Reject\",\n \"template\": \"https://server.org/intents/reject?objectId={object}\"\n}\n"},{"location":"fep/3b86/#423-tentativeaccept-intent","title":"4.23. TentativeAccept Intent","text":"This intent corresponds to the ActivityStreams TentativeAccept activity, which itself is a specialization of the Accept activity and is defined using the link relation https://w3id.org/fep/3b86/TentativeAccept.
The TentativeAccept intent publishes the API endpoint where the current user can initiate an \"tentative accept\" request, indicating that acceptance of the original offer is tentative.
"},{"location":"fep/3b86/#4231-parameters","title":"4.23.1. Parameters","text":"{object} - ID of the object that the user will tentatively accept when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/TentativeAccept\",\n \"template\": \"https://server.org/intents/tentativeAccept?objectId={object}\"\n}\n"},{"location":"fep/3b86/#424-tentativereject-intent","title":"4.24. TentativeReject Intent","text":"This intent corresponds to the ActivityStreams TentativeReject activity, which itself is a specialization of the Reject activity and is defined using the link relation https://w3id.org/fep/3b86/TentativeReject.
The TentativeReject intent publishes the API endpoint where the current user can initiate an \"tentative reject\" request, indicating that rejection of the original offer is tentative.
"},{"location":"fep/3b86/#4241-parameters","title":"4.24.1. Parameters","text":"{object} - ID of the object that the user will tentatively reject when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/TentativeReject\",\n \"template\": \"https://server.org/intents/tentativeReject?objectId={object}\"\n}\n"},{"location":"fep/3b86/#425-travel-intent","title":"4.25. Travel Intent","text":"This intent corresponds to the ActivityStreams Travel activity and is defined using the link relation https://w3id.org/fep/3b86/Travel.
The Travel intent publishes the API endpoint where the user can initiate a \"travel\" request.
"},{"location":"fep/3b86/#4251-parameters","title":"4.25.1. Parameters","text":"{target} - (optional) The ID of the location that the actor will travel to.{origin} - (optional) The ID of the location that the actor will travel from.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Travel\",\n \"template\": \"https://server.org/intents/travel?from={origin}&to={target}\"\n}\n"},{"location":"fep/3b86/#426-undo-intent","title":"4.26. Undo Intent","text":"This intent corresponds to the ActivityStreams Undo activity and is defined using the link relation https://w3id.org/fep/3b86/Undo.
The Undo intent publishes the API endpoint where the current user can initiate a \"travel\" request.
"},{"location":"fep/3b86/#4261-parameters","title":"4.26.1. Parameters","text":"{object} - ID of the activity that the actor will undo.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) URL to redirect users if they abort the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Undo\",\n \"template\": \"https://server.org/intents/undo?activityId={object}\"\n}\n"},{"location":"fep/3b86/#427-update-intent","title":"4.27. Update Intent","text":"This intent corresponds to the ActivityStreams Update activity and is defined using the link relation https://w3id.org/fep/3b86/Update.
The Update intent publishes the API endpoint where the user can initiate an \"update\" request.
"},{"location":"fep/3b86/#4271-parameters","title":"4.27.1. Parameters","text":"{object} - ID of the object that the actor will update when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Update\",\n \"template\": \"https://server.org/intents/update?objectId={object}\"\n}\n"},{"location":"fep/3b86/#428-view-intent","title":"4.28. View Intent","text":"This intent corresponds to the ActivityStreams View activity and is defined using the link relation https://w3id.org/fep/3b86/View.
The View intent publishes the API endpoint where the current user can initiate an \"update\" request.
"},{"location":"fep/3b86/#4281-parameters","title":"4.28.1. Parameters","text":"{object} - ID of the object that the actor will mark as \"viewed\" when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/View\",\n \"template\": \"https://server.org/intents/view?objectId={object}\"\n}\n"},{"location":"fep/3b86/#50-additional-intent-definitions","title":"5.0. Additional Intent Definitions","text":"In addition to the activities defined in the Activity Vocabulary spec, this FEP defines a few additional intents that home severs SHOULD publish, and remote servers MAY implement.
"},{"location":"fep/3b86/#51-object-intent","title":"5.1 Object Intent","text":"This intent corresponds to the ActivityStreams Object definition, and is intended as a way for users to open an object from a remote server directly in their home server. This is analogous to copying the URL of the object in question, then pasting the URL into the search box of the user's home server.
This intent only opens the object using the user's home server. It does not trigger an ActivityPub activity. Because of this, it does not use the on-success and on-cancel workflows because there is no action to succeed, and no action to cancel.
{object} - ID of the object that the actor will open in their home server when they use this workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Object\",\n \"template\": \"https://server.org/intents/object?objectId={object}\"\n}\n"},{"location":"fep/3b86/#60-security-considerations","title":"6.0. Security Considerations","text":""},{"location":"fep/3b86/#61-csrf-issues","title":"6.1 CSRF issues","text":"It is important to reiterate some key security practices to prevent Cross Site Request Forgery vulnerabilities.
GET requests to Home Servers.Activity Intents includes the ability for home servers to redirect browsers to new URLs based on the on-success and on-cancel query parameters. If improperly implemented, this has the potential to expose an Unvalidated Redirect vulnerability, which can open the home server up to phishing attack.
To avoid a potential security vulnerability when handling on-success and on-cancel workflows, it is important that the home server: \"Force all redirects to first go through a page notifying users that they are going off of your site, with the destination clearly displayed, and have them click a link to confirm.\" (owasp.org)
There is another good description of this issue OAuth 2.0 Security Best Current Practice \u00a7 4.11. Open Redirection
"},{"location":"fep/3b86/#70-remote-servers-the-rest-of-the-equation","title":"7.0. Remote Servers: The Rest of the Equation","text":"This FEP provides the prerequisite information required for a \"Home Server\" publish Activity Intents for its Actors. It does not specify how \"Remote Servers\" will use this information - i.e., how they implement \"share\" and \"like\" buttons in their content.
"},{"location":"fep/3b86/#71-remote-server-example","title":"7.1. Remote Server Example","text":"Here is a brief example of the workflow as implemented by a remote server:
localStorage)on-success parameter to redirect the user back to the correct page on the remote server.on-cancel parameter to redirect the user back to the correct page on the remote server.Remote servers MAY also account for applications that do not publish Activity Intents, but whose endpoints are still well known. In this case, remote servers SHOULD use Activity Intents links if they are present, then fall back to older links (such as the oStatus /authorize_interaction endpoint) if they are present, then fall back to hard-coded values (such as the Mastodon /share endpoint) if no other values exist.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/400e/","title":"FEP-400e: Publicly-appendable ActivityPub collections","text":"Authors Status Date received Date final Tracking issue Discussions Repository Gregory Klyushnikov activitypub@grishka.meFINAL 2021-02-16 2022-02-04 #5 - codeberg"},{"location":"fep/400e/#summary","title":"Summary","text":"In social media, it's a frequent pattern when there's a collection owned by someone that other people can contribute to. Examples include:
Currently, there is no generic way to signify that an object was created as part of a collection and should only be considered in its context.
This proposal describes how ActivityPub servers and clients could specify collections to which objects created by their actors belong.
"},{"location":"fep/400e/#requirements","title":"Requirements","text":"The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].
"},{"location":"fep/400e/#publicly-appendable-collections","title":"Publicly-appendable collections","text":"A publicly-appendable collection is any collection where it is expected that someone other than its owner could add items but over which its owner retains complete authority. For example, a wall is a collection to which other people could add posts (Notes), but from which its owner could delete any posts as well as restrict who can add them.
A publicly-appendable collection SHOULD have a valid and globally-unique id that SHOULD point to either a Collection or an OrderedCollection object that contains the links to all its objects.
If an actor has publicly-appendable collections, its server MAY include them as additional fields in its ActivityPub representation. For example, user actors may specify the link to their walls, or groups may specify the link to the collection of their photo albums.
Implementations MAY use the presence or absence of specific collection to determine whether the actor's server supports features that depend on that collection and alter their UIs accordingly.
"},{"location":"fep/400e/#using-target-in-objects","title":"Usingtarget in objects","text":"If an ActivityPub object is being created as part of a collection, the object SHOULD include the target field that contains an abbreviated collection object, which SHOULD contain at least the following fields:
type \u2014 either Collection or OrderedCollection.id \u2014 the id of the collection.attributedTo \u2014 the id of the owner of the collection. This is necessary to simplify the database design on the receiving side.While [Activity Vocabulary] specifies target as a field with similar semantics in activities, it's important to include it in objects themselves so any software that only sees the object without its enclosing Create activity, e.g. when following a link form another object or retrieving the object from a user-provided URI, unambiguously knows that it should only be considered in the context of its collection.
When an ActivityPub server receives in its inbox a correctly signed Create activity with an object that has the target field, it does the following:
attributedTo or id fields of the abbreviated collection object.attributedTo doesn't match the actual owner of the collection specified by id, or if the collection owner is not a local actor, the server SHOULD abort processing and MAY return 400 Bad Request.403 Unauthorized or respond with 200 OK and later send a Reject{Create} activity to the originating server.id in its local storage as belonging to the specified collection.Add activity to any parties that might be concerned with it. The target field in the activity SHOULD only be the collection id, and the object field SHOULD be the id of the object being added. It is RECOMMENDED that this activity is sent to all the collection owner's followers for the sake of data consistency, and it SHOULD be sent to the actor that created the object being added.Add activity","text":"When an ActivityPub server receives in its inbox a correctly signed Add activity, it SHOULD do the following:
actor either from local storage or from the network.object.target is the ID of a collection owned by actor. If it is not, abort processing and return 400 Bad Request.target in the activity matches target.id in the object and that target.attributedTo in the object matches the actor ID. If it does not, abort processing and return 400 Bad Request.id in its local storage as belonging to the specified collection.Add activity","text":"{\n \"@context\":\"https://www.w3.org/ns/activitystreams\",\n \"actor\":\"https://example.com/users/1\",\n \"id\":\"https://example.com/posts/41864/activityAdd\",\n \"to\":[\n \"https://www.w3.org/ns/activitystreams#Public\",\n \"https://example.com/users/1/followers\",\n \"https://example.com/users/6946\"\n ],\n \"type\":\"Add\",\n \"object\":\"https://example.com/posts/41864\",\n \"target\":\"https://example.com/users/1/wall\"\n}\n"},{"location":"fep/400e/#deleting-an-object-from-a-collection","title":"Deleting an object from a collection","text":"Since the collection owner has complete authority over the contents of the collection, they can delete any objects from it. When an object is deleted from a collection by its owner, their server SHOULD send a Delete activity to at least the server of the actor that created the object; it's also RECOMMENDED that this activity is sent to all the servers that Add was sent to. Those servers then SHOULD delete the object as if the deletion was initiated by its creator.
In some use cases, it might make sense to allow objects to be moved between collections, for example, a group moderator might want to move a photo between photo albums in a group, or a forum moderator might want to split some messages into a separate thread. It's only possible to move objects between collections that are owned by the same actor.
When moving an object between collections, the collection owner SHOULD send a Move activity to at least the server of the object creator, specifying the target collection and the id of the object; it's also RECOMMENDED that this activity is sent to all the servers that Add was sent to. Those servers then SHOULD update the target field in their stored copies of the object.
The requirement of an Add activity sent by a collection owner largely prevents a bad actor from effectively adding something to a collection against the collection owner's will while also helping data consistency across servers. However, there's still one case when this is possible. When a server fetches an object that has a target field directly, for example when a user has entered its URL into a search box or when it is referenced by a field such as inReplyTo, there's presently no reliable way to verify whether the object actually belongs to the collection.
This proposal is implemented in Smithereen for both user and group walls since the following commit: https://github.com/grishka/Smithereen/commit/de013593dde06a3091ecfbd32960a694d79c146e
"},{"location":"fep/400e/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/4adb/","title":"FEP-4adb: Dereferencing identifiers with webfinger","text":"Authors Status Date received Tracking issue Discussions Repository Helge helge.krueger@gmail.comDRAFT 2023-05-13 #100 - codeberg"},{"location":"fep/4adb/#summary","title":"Summary","text":"In this FEP, we will formalize the process of dereferencing an URI using webfinger in order for usage in ActivityPub. The main goal is to enable the usage of URIs of the form acct:user@domain or did:example:12345 as ids for objects used in ActivityPub. While this FEP only discusses this in the context of actors, it should be applicable for general objects. In order for a smooth introduction, it is recommended to start deployment with actor objects.
This FEP first presents the algorithm and examples, then discusses the usage in the context of the Fediverse. This means the first two sections are for people wanting to implement this FEP, the following sections are for people wanting to decide if this FEP is a good idea.
"},{"location":"fep/4adb/#the-algorithm","title":"The algorithm","text":"[Webfinger] allows us to associate an ActivityStreams object with a pair given by an URI and a domain. This is done through the following steps:
GET https://domain/.well-known/webfinger?resource={URI}.\"type\":\"application/activity+json\".We will denote this object by ActivityStreams(URI, domain). There are now two cases to resolve an URI:
domain(URI)In case 1, it is clear that we associate ActivityStreams(URI, domain(URI)) to the URI. In case 2, we will use the domain associated with the @id of the document the document the URI appeared in. If the document was received through a POST request and doesn't contain an id, or the id is an URI, the domain the POST request originated from should be used. In this case a verification that the URI can be associated with the object MUST be performed. This can for example be achieved through FEP-c390.
We start with the acct URI Scheme. In order to deliver the activity
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Follow\",\n \"actor\": \"https://chatty.example/ben/\",\n \"object\": \"acct:alyssa@social.example\",\n \"to\": \"acct:alyssa@social.example\",\n}\n the ActivityPub server should perform the lookup for ActivityStreams(\"acct:alyssa@social.example\", \"social.example\"), i.e. the request
GET https://social.example/.well-known/webfinger?resource=acct:alyssa@social.example\n We now turn our attention to DIDs. In order to resolve the author of
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://chatty.example/ben/456\",\n \"type\": \"Note\",\n \"attributedTo\": \"did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5\",\n \"to\": \"acct:alyssa@social.example\",\n \"content\": \"Hello\"\n}\n one has to resolve the URI did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5. As this URI does not contain a domain, the domain from the id, i.e. chatty.example is used, so the request
GET https://chatty.example/.well-known/webfinger?resource=did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5\n is made. The resulting ActivityStreams object should contain
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://chatty.example/bnm789\",\n \"attachment\": {\n \"type\": \"VerifiableIdentityStatement\",\n \"subject\": \"did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5\",\n \"alsoKnownAs\": \"https://chatty.social/bnm789\",\n \"proof\": { ... }\n },\n ...\n}\n following FEP-c390 so that the authority of ben to use the did can be verified.
"},{"location":"fep/4adb/#background","title":"Background","text":"Let's start with Section 3.1 of ActivityPub. It starts with the sentence
All Objects in [ActivityStreams] should have unique global identifiers.
We first note that the word unique does not appear in [ActivityStreams]. Second it is unclear why it is there and what it means:
We will assume that it is the second interpretation. In particular, this means that we can associated multiple identifiers with the same ActivityStreams object.
Next comes the following line in [ActivityPub]
Publicly dereferencable URIs, such as HTTPS URIs, with their authority belonging to that of their originating server. (Publicly facing content SHOULD use HTTPS URIs).
The essential point of this FEP is to extend the range of publicly dereferencable URIs to contain basically any URI by using [Webfinger].
The current usage of Webfinger in the Fediverse is asymmetric. As discussed in MastoGuide, one can associate many URIs of the form acct:user@domain.tld with the same Actor, by just making webfinger return an appropriate response. However, only one acct-URI can be associated with an Actor. This is done by:
acct:{preferredUsername}@{domain of actor id}\n where preferredUsername is from the actor object.
We now discuss applications of the approach of this FEP to the actor object.
"},{"location":"fep/4adb/#preferred-account","title":"Preferred Account","text":"We will follow this suggestion in [alsoKnownAs and acct:]. The main idea is to associate multiple accounts. For this, we will return to alyssa@social.example, whose Actor object would look like the following omitting irrelevant details
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://social.example/abc123\",\n \"preferredUsername\": \"alyssa\",\n ...\n}\n By the algorithm discussed in background, we can associate the URI acct:alyssa@social.example with this. Suppose now that Alyssa owns the domain alyssa.cool, and set up WebFinger to answer to
GET https://alyssa.cool/.well-known/webfinger?resource=me@alyssa.cool\n with a link to the above actor. Alyssa would then be disappointed because Fediverse software is not displaying her cooler username. To remedy this, we propose the addition to the actor object
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"xrd\": \"http://docs.oasis-open.org/ns/xri/xrd-1.0#\",\n \"aliases\": {\n \"@id\": \"xrd:Alias\",\n \"@type\": \"@id\",\n \"@container\": \"@list\"\n },\n }\n ],\n \"id\": \"https://social.example/abc123\",\n \"preferredUsername\": \"alyssa\",\n \"aliases\": [\"acct:me@alyssa.cool\", \"acct:alyssa@social.example\"],\n ...\n}\n a new generation of Fediverse software will then be able to display the account of Alyssa as me@alyssa.cool.
Continuing with Alyssa from above, by relying on FEP-612d, she may even associate @alyssa.cool with her account. Unfortunately, a proper URI format is still missing.
Let's return to Ben and him wanting to use a decentralized identifier, see did-core and did-method-key
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"xrd\": \"http://docs.oasis-open.org/ns/xri/xrd-1.0#\",\n \"aliases\": {\n \"@id\": \"xrd:Alias\",\n \"@type\": \"@id\",\n \"@container\": \"@list\"\n },\n }\n ],\n \"id\": \"https://chatty.social/bnm789\",\n \"preferredUsername\": \"ben\",\n \"aliases\": [\"did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5\", \"acct:ben@chatty.social\"],\n ...\n}\n The problem here is that, we do not know if the the did-key belongs to BEN. To remedy this, we will use FEP-c390. This means that we need to add
\"attachment\": {\n \"type\": \"VerifiableIdentityStatement\",\n \"subject\": \"did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5\",\n \"alsoKnownAs\": \"https://chatty.social/bnm789\",\n \"proof\": { ... }\n }\n with an appropriate proof to Ben's actor object.
"},{"location":"fep/4adb/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/4ccd/","title":"FEP-4ccd: Pending Followers Collection and Pending Following Collection","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@prodromou.nameDRAFT 2023-06-21 #129 Discussions codeberg"},{"location":"fep/4ccd/#summary","title":"Summary","text":"This ActivityPub extension defines two collections, pendingFollowers and pendingFollowing, with which users can review and manage their pending follow requests.
ActivityPub represents a directed social graph with followers and following properties on actors. To initiate a relationship, a Follow activity is sent from the potential follower to the followed actor, who can Accept or Reject it.
Between the time that a Follow is sent and the time that it is accepted or rejected, the relationship is in a pending state. This is not represented in the ActivityPub data model.
The new pendingFollowers collection can be used to review incoming Follow activities to Accept or Reject them.
The new pendingFollowing collection can be used to review outgoing Follow activities to Undo them.
Because the full activity data is needed to Accept, Reject or Undo, these collections should include Follow activities, and not just the actors requesting to follow.
Note that this extension is primarily useful for clients and servers that implement the ActivityPub API. Furthermore, it is primarily useful for actors that manually approve followers. The manuallyApprovesFollowers property in the ActivityPub Miscellaneous Terms can be used to indicate that an actor manually approves followers.
"},{"location":"fep/4ccd/#context","title":"Context","text":"The context document for this ActivityPub extension is at https://purl.archive.org/socialweb/pending. Its contents are as follows:
{\n \"@context\": {\n \"pdg\": \"https://purl.archive.org/socialweb/pending#\",\n \"pendingFollowers\": {\n \"@id\": \"pdg:pendingFollowers\",\n \"@type\": \"@id\"\n },\n \"pendingFollowing\": {\n \"@id\": \"pdg:pendingFollowing\",\n \"@type\": \"@id\"\n },\n \"pendingFollowersOf\": {\n \"@id\": \"pdg:pendingFollowersOf\",\n \"@type\": \"@id\"\n },\n \"pendingFollowingOf\": {\n \"@id\": \"pdg:pendingFollowingOf\",\n \"@type\": \"@id\"\n }\n }\n}\n"},{"location":"fep/4ccd/#semantic-versioning","title":"Semantic versioning","text":"For compatibility, the context document for this extension has aliases using semantic versioning.
https://purl.archive.org/socialweb/pending/1.1.0. This version is immutable.https://purl.archive.org/socialweb/pending/1.1. This version may be updated with bug fixes, documentation, or minor changes, but no new terms.https://purl.archive.org/socialweb/pending/1. This version may be updated with bug fixes or minor changes, and may include new terms, but will not include breaking changes.https://purl.archive.org/socialweb/pending. This version may be updated with bug fixes or minor changes, may include new terms, and may include breaking changes. It is the latest version of the context document.All terms in the context document use the same namespace, https://purl.archive.org/socialweb/pending#.
As with other ActivityPub properties, the values of these properties can be included by a reference URL, or by an embedded node object.
"},{"location":"fep/4ccd/#pendingfollowers","title":"pendingFollowers","text":"URI https://purl.archive.org/socialweb/pending#pendingFollowers Notes pendingFollowers is a collection of Follow activities that have been sent to the actor, but have not yet been accepted or rejected. Items in the collection MUST be in reverse chronological order. Items in the collections MUST be Follow activities. They MUST be unique by id. Each actor of a Follow activity in the collection MUST be unique by id. Domain Object (ActivityPub actor) Range OrderedCollection or Collection Functional Yes"},{"location":"fep/4ccd/#pendingfollowing","title":"pendingFollowing","text":"URI https://purl.archive.org/socialweb/pending#pendingFollowing Notes pendingFollowing is a property of an actor. It is a collection of Follow activities that have been sent by the actor, but have not yet been accepted or rejected. They MUST be unique by id. Each object of a Follow activity in the collection MUST be unique by id. Domain Object (ActivityPub actor) Range OrderedCollection or Collection Functional Yes"},{"location":"fep/4ccd/#pendingfollowersof","title":"pendingFollowersOf","text":"URI https://purl.archive.org/socialweb/pending#pendingFollowersOf Notes This property identifies the actor for which the specified collection is the pendingFollowers collection. It is an inverse property of pendingFollowers. Domain Collection or OrderedCollection Range Object (ActivityPub actor) Functional Yes"},{"location":"fep/4ccd/#pendingfollowingof","title":"pendingFollowingOf","text":"URI https://purl.archive.org/socialweb/pending#pendingFollowingOf Notes This property identifies the actor for which the specified collection is the pendingFollowing collection. It is an inverse property of pendingFollowing. Domain Collection or OrderedCollection Range Object (ActivityPub actor) Functional Yes"},{"location":"fep/4ccd/#examples","title":"Examples","text":"A publisher can include the pendingFollowers and pendingFollowing collection in the properties of an actor.
pendingFollowers and pendingFollowing","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/pending/1\"\n ],\n \"id\": \"https://example.com/evanp\",\n \"type\": \"Person\",\n \"name\": \"Evan Prodromou\",\n \"inbox\": \"https://example.com/evanp/inbox\",\n \"outbox\": \"https://example.com/evanp/outbox\",\n \"following\": \"https://example.com/evanp/following\",\n \"followers\": \"https://example.com/evanp/followers\",\n \"liked\": \"https://example.com/evanp/liked\",\n \"pendingFollowers\": \"https://example.com/evanp/pendingFollowers\",\n \"pendingFollowing\": {\n \"id\": \"https://example.com/evanp/pendingFollowing\",\n \"type\": \"Collection\",\n \"name\": \"Pending following for Evan Prodromou\",\n \"totalItems\": 2\n },\n \"manuallyApprovesFollowers\": true,\n \"to\": [\"as:Public\"]\n}\n Notice that the pendingFollowers property is a URL, while the pendingFollowing property is an embedded object with useful additional properties.
pendingFollowers collection","text":"Retrieving the pendingFollowers collection shows incoming follow requests for this actor.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/pending/1\"\n ],\n \"id\": \"https://example.com/evanp/pendingFollowers\",\n \"type\": \"OrderedCollection\",\n \"attributedTo\": \"https://example.com/evanp\",\n \"pendingFollowersOf\": \"https://example.com/evanp\",\n \"name\": \"Pending followers for Evan Prodromou\",\n \"orderedItems\": [\n {\n \"type\": \"Follow\",\n \"id\": \"https://example.net/alyssa/follow/7\",\n \"summary\": \"Alyssa wants to follow Evan\",\n \"content\": \"Hey, Evan! It's Alyssa from the conference.\",\n \"actor\": {\n \"id\": \"https://example.net/alyssa\",\n \"type\": \"Person\",\n \"name\": \"Alyssa P. Hacker\"\n },\n \"to\": \"https://example.com/evanp\",\n \"cc\": \"as:Public\",\n \"published\": \"2023-06-21T12:00:00Z\"\n },\n {\n \"type\": [\"http://custom.example/ns/Archive\", \"Follow\"],\n \"id\": \"https://social.example/jokebot3000/follow/287\",\n \"summary\": \"Jokebot 3000 wants to follow Evan to archive his jokes\",\n \"actor\": {\n \"id\": \"https://social.example/jokebot3000\",\n \"type\": \"Application\",\n \"name\": \"Jokebot 3000\"\n },\n \"to\": \"https://example.com/evanp\",\n \"cc\": \"as:Public\",\n \"published\": \"2023-05-07T12:00:00Z\"\n }\n ]\n}\n Note that the second, earlier Follow activity has a custom type property. Note also that the object of the Follow activities, which will be the same for every activity, is elided for clarity and space.
pendingFollowing collection","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/pending/1\",\n {\"sports\": \"https://sports.example/ns#\"}\n ],\n \"id\": \"https://example.com/evanp/pendingFollowing\",\n \"type\": \"Collection\",\n \"attributedTo\": \"https://example.com/evanp\",\n \"pendingFollowingOf\": \"https://example.com/evanp\",\n \"name\": \"Pending following for Evan Prodromou\",\n \"items\": [\n {\n \"type\": [\"sports:Fan\", \"Follow\"],\n \"id\": \"https://example.com/evanp/fan/309\",\n \"summary\": \"Evan is a fan of Jimena\",\n \"actor\": \"https://example.com/evanp\",\n \"object\": {\n \"id\": \"https://tennis.example/jimena\",\n \"type\": \"Person\",\n \"name\": \"Jimena Suarez\"\n },\n \"to\": \"https://tennis.example/jimena\",\n \"cc\": \"as:Public\",\n \"published\": \"2023-04-19T12:00:00Z\"\n },\n {\n \"type\": \"Follow\",\n \"id\": \"https://example.net/evanp/follow/214\",\n \"summary\": \"Evan wants to follow Montreal Weather Updates\",\n \"actor\": \"https://example.com/evanp\",\n \"object\": {\n \"id\": \"https://weather.example/canada/quebec/montreal\",\n \"type\": \"Service\",\n \"name\": \"Montreal Weather Updates\"\n },\n \"to\": \"https://weather.example/canada/quebec/montreal\",\n \"cc\": \"as:Public\",\n \"published\": \"2023-02-11T12:00:00Z\"\n }\n ]\n}\n Note that the first Follow activity has a custom type property. Also note that even though the collection's type is Collection and the items property is items, the activities still must be in reverse chronological order.
An actor that manually approves followers SHOULD include the manuallyApprovesFollowers property in their actor object, with a value of true. This indicates that the actor will review and approve or reject incoming follow requests.
When a server receives an otherwise valid Follow activity from a client, it SHOULD add that activity to the pendingFollowing collection of the sending actor. The server SHOULD also add the Follow activity to the pendingFollowers collection of the actor that is being followed, if the followed actor is on the same server.
When a server receives an otherwise valid Follow activity from another server, it SHOULD add that activity to the pendingFollowers collection of the followed actor.
When a server receives an Accept or Reject activity with a Follow activity as object from a client, it SHOULD remove that Follow activity from the pendingFollowers collection of the actor. The server SHOULD also remove the Follow activity from the pendingFollowing collection of the actor that initiated the follow, if the following actor is on the same server.
When a server receives an Accept or Reject activity with a Follow activity as object from another server, it SHOULD remove that Follow activity from the pendingFollowing collection of the receiving actor.
When a server receives an Undo activity with a Follow activity as object from a client, it SHOULD remove that Follow activity from the pendingFollowing collection of the actor that sent the Follow. It SHOULD also remove the Follow activity from the pendingFollowers collection of the actor that was followed, if the followed actor is on the same server.
When a server receives an Undo activity with a Follow activity as object from another server, it SHOULD remove that Follow activity from the pendingFollowers collection of the receiving actor.
The following Harel statechart illustrates the state transitions for a Follow activity between actors A1 and A2. The states show which objects belong to which collections, and the transitions show when activities are received and processed via the client API and then the server federation protocol.
The pendingFollowers and pendingFollowing collections are sensitive information about an actor's social connections. For privacy, some services and actors do not share the following or followers collections. If not similarly protected, the pendingFollowers and pendingFollowing collections could be used to infer information about the actor's social connections before they are established.
Some services or actors do not forward Reject activities to the actor of a Follow activity. Harassing or abusive actors may try to determine if the actor has rejected their follow request by fetching the pendingFollowers collection.
For these reasons, publishers SHOULD NOT make the pendingFollowers and pendingFollowing collections visible to unauthenticated users. Publishers SHOULD NOT make the pendingFollowers and pendingFollowing collections visible to authenticated users who are not the actor.
pendingFollowers and pendingFollowing collections.pendingFollowers and pendingFollowing collections.CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/4f05/","title":"Index","text":"hide: - navigation description: \"The standard CRUD (Create, Read, Update, Delete) behaviours in ActivityPub specify a single Delete activity for use in all cases. This is insufficient to describe two-stage deletion, often referred to as \"soft\" and \"hard\" deletion. Not all software implements two-stage deletion, and so the behaviours described here progressively enhance the functionality for those supporting it, while retaining backward compatibility otherwise.\"
"},{"location":"fep/4f05/#fep-4f05-soft-deletion","title":"FEP-4f05: Soft Deletion","text":"Authors Status Date received Tracking issue Discussions Repository Julian Lam julian@nodebb.org, Angus McLeod angus@pavilion.techDRAFT 2025-04-15 #554 Discussions codeberg"},{"location":"fep/4f05/#summary","title":"Summary","text":"The standard CRUD (Create, Read, Update, Delete) behaviours in ActivityPub specify a single Delete activity for use in all cases. This is insufficient to describe two-stage deletion, often referred to as \"soft\" and \"hard\" deletion.
Not all software implements two-stage deletion, and so the behaviours described here progressively enhance the functionality for those supporting it, while retaining backward compatibility otherwise.
"},{"location":"fep/4f05/#assumptions","title":"Assumptions","text":"A blog post by kaniini advocates for the treatment of copies of remote data as a cached representation. From there we derive the following assumptions:
Delete activity should be treated as a request to refresh the locally cached copy or delete it otherwise.The Forums and Threaded Discussions Task Force (ForumWG) has identified a common nomenclature when referring to organized objects in a threaded discussion model.
When an object is soft deleted, the object's ActivityPub representation MUST be updated to Tombstone. Servers SHOULD continue to respond to requests for the object with a 200-level response code; the object continues to exist in-place.
A Delete activity SHOULD be published in order to propagate the soft deletion to other servers.
When an object is hard deleted, the object MUST no longer have an ActivityPub representation. Servers MUST respond to requests for the object with a 400-level response code. A 404 Not Found is acceptable, although a 410 Gone sends a more explicit signal that the object was explicitly removed. Security or privacy considerations may affect your decision to send anything more than a 404.
A Delete activity MUST be published in order to propagate the hard deletion to other servers.
When a Delete activity is encountered, the referenced object MAY be either the full object or a reference to one.
Verifying authenticity of the embedded object is out of scope of this FEP. Follow the origin-based security model for verifying authenticity of any received Delete activity.
If object is a reference, the server MUST request the object (via its id) from the origin server directly.
Handle the received activity as below based on the received response code or object type.
Note
The actor may not match the attributedTo of the targeted object. This is allowed as moderators or privileged users may carry out deletions.
Tombstone","text":"The local object SHOULD be soft deleted as per the local implementor's standard behaviour.
"},{"location":"fep/4f05/#not-a-tombstone","title":"Not aTombstone","text":"Update the object's local representation if applicable.
"},{"location":"fep/4f05/#http-404-or-410","title":"HTTP 404 or 410","text":"The local object SHOULD be hard deleted as per the local implementor's standard behaviour.
"},{"location":"fep/4f05/#unexpected-responses","title":"Unexpected responses","text":"The section above (\"Receivers\") details how a received Delete activity is handled. If during the course of the backreference check the object type or response code differs from expectations, the retrieved state supercedes the activity.
e.g. A Delete is received, but a backreference check returns a 200 with a Note-type object. Therefore it is \"Not a Tombstone\" even though the activity received stated otherwise.
The inverse also applies.
e.g. An Undo(Delete) is received, but a backreference check returns a Tombstone. There fore it is still treated as a Tombstone even though the activity received stated otherwise.
An earlier implementation of two-stage object deletion published an Update(Tombstone), but this approach was deemed superfluous as it signified the same effect as a Delete\u2014to proceed with a cache invalidation and update.
It is safe to assume the majority of ActivityPub-enabled software does not support two-stage object deletion. Publishing a Delete ensures that the intended behaviour of a soft deletion\u2014that the object's content is no longer visible\u2014is carried through to other servers.
Implementors are free to handle a soft deletion in the way they prefer (e.g. NodeBB will continue to associate the post (object) with the original actor, and simply blank out the content for non-privileged users.) This FEP explicitly does not specify how individual implementors should handle local representations of remote data.
"},{"location":"fep/4f05/#who-to-address","title":"Who to address","text":"The recipients list of the published Delete activity is outside the scope of this document.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/521a/","title":"FEP-521a: Representing actor's public keys","text":"Authors Status Type Date received Date final Tracking issue Discussions Repository silverpill @silverpill@mitra.socialFINAL implementation 2023-07-08 2025-06-14 #130 Discussions codeberg"},{"location":"fep/521a/#summary","title":"Summary","text":"This proposal describes how to represent public keys associated with ActivityPub actors.
"},{"location":"fep/521a/#rationale","title":"Rationale","text":"Historically, Fediverse services used publicKey property to represent actor's public key. Implementations usually allow only one key per actor, therefore a new approach is needed to support use cases where additional keys are required.
Furthermore, publicKey property is considered deprecated and is not present in the latest version of Security Vocabulary.
The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/521a/#multikey","title":"Multikey","text":"Each public key MUST be represented as an object with Multikey type, as defined in section 2.2.2 Multikey of Controlled Identifiers specification. This object MUST have the following properties:
id: the unique global identifier of the public key.type: the value of this property MUST be the string Multikey.controller: the value of this property MUST match actor ID.publicKeyMultibase: a Multibase encoded value of a Multicodec prefix and the public key. Implementations MUST use the base-58-btc alphabet.Key identifiers MUST be absolute URIs.
Identifiers SHOULD be generated by appending a fragment identifier to the actor ID. That allows consumers to retrieve both actor object and associated keys with a single HTTP request. If actor's keys are identified using fragment identifiers, each key MUST have a unique fragment identifier.
Resolution of a URI containing a fragment identifier is performed using the algorithm specified in section 3.4 Fragment Resolution of Controlled Identifiers specification.
"},{"location":"fep/521a/#key-types","title":"Key types","text":"Implementers can use cryptographic keys of any type for which a Multicodec prefix is registered.
"},{"location":"fep/521a/#adding-keys-to-an-actor-object","title":"Adding keys to an actor object","text":"Multikey objects are added to the actor object, which is considered a controlled identifier document, as described in Controlled Identifiers specification.
If the key is intended to be used for signing ActivityPub objects, it MUST be added to the assertionMethod array in the actor object.
Other use cases are out of scope of this proposal.
Implementations are discouraged from adding any objects to the assertionMethod array that do not conform to this specification. Implementations encountering non-conformant entries in the assertionMethod array SHOULD ignore them.
An actor MAY have no associated public keys.
"},{"location":"fep/521a/#example","title":"Example","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/cid/v1\"\n ],\n \"type\": \"Person\",\n \"id\": \"https://server.example/users/alice\",\n \"inbox\": \"https://server.example/users/alice/inbox\",\n \"outbox\": \"https://server.example/users/alice/outbox\",\n \"assertionMethod\": [\n {\n \"id\": \"https://server.example/users/alice#ed25519-key\",\n \"type\": \"Multikey\",\n \"controller\": \"https://server.example/users/alice\",\n \"publicKeyMultibase\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n }\n ]\n}\n"},{"location":"fep/521a/#security-considerations","title":"Security considerations","text":"Each actor is expected to have a different secret key.
To prevent side-channel attacks, secret keys need to be stored in binary form. If stored as text, they need to be decoded using a constant-time algorithm.
Warning
Controlled Identifiers specification requires implementers to use the base-58-btc alphabet for multibase-encoded secret keys. This practice is discouraged because available implementations of Base58 decode algorithm may not be constant-time.
See fep-521a.feature
"},{"location":"fep/521a/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/521a/fep-521a-test-case/","title":"Actor Objects must express signing key as assertionMethod Multikey","text":""},{"location":"fep/521a/fep-521a-test-case/#background","title":"Background","text":"FEP-521a defines how Actor object MUST express the public key for its signing key in the assertionMethod property, as a Multikey object of a given shape and id URI shape.
This is a Test Case testing conformance with the one behavior specified in FEP-521a.
"},{"location":"fep/521a/fep-521a-test-case/#test-subject","title":"Test Subject","text":"The subject of this test is any data claiming to conform to the specification of an ActivityPub Actor Object and to the FEP-521a extension thereof.
This test is not directly applicable to an ActivityPub Server. An ActivityPub Server serves 0 or more Actor Objects. An ActivityPub Server for a big community might serve hundreds of ActivityPub Actor Objects. An ActivityPub Server for a single human may serve only that person's ActivityPub Actor Object.
This test applies to Actor Objects, but not all Actor Objects are addressable by an HTTPS URL. The URI that addresses an Actor Object is not the same as the Actor Object. A given URL may resolve to different Actor Objects in different contexts, and a given Actor Object may not be universally addressable across context by any one URL.
"},{"location":"fep/521a/fep-521a-test-case/#inputs","title":"Inputs","text":"This test requires the following inputs:
actor - the actor object under testedinapplicable.inapplicable.assertionMethod arrayinapplicable.This test applies directly to the actor input.
actor is not an Actor Object, the outcome MUST be inapplicable.actor is not a JSON object, the outcome MUST be inapplicable.actor JSON does not have a type property, the outcome MUST be inapplicable.actor JSON does not have an assertionMethod property, the outcome MUST be inapplicable.assertionMethod array is a distinct test target, referred to below as assertionMethod[x]Multikey is inapplicable (warning)Multikey passes or fails the tests of its validity as a MultikeyassertionMethod[x].id - MUST be a stringassertionMethod[x].controller - MUST match the id property, if present, of the assertionMethod array's parent (i.e., the Actor object)assertionMethod[x].publicKeyMultibase - MUST be a base58btc-encoded and appropriately-prefixed (in this case, beginning with z) expression of a binary public key expressionFor the purposes of determining whether the input actor is expressing a key in the shape defined by the FEP:
assertionMethod property, containing an array of 0 or more objectsid property, containing a stringtype property, containing a stringcontroller property, containing a stringpublicKeyMultibase property, containing a stringFor the purposes of determining whether the input actor is expressing 1 or more keys all the following must be true:
assertionMethod array, once parsed as JSON, ...id property, which is a URIassertionMethod.id property, which is a string unique within the actor objectassertionMethod.type property whose value is the string \"Multikey\" or an Array containing the string \"Multikey\"assertionMethod.controller property whose value is a string identical to the parent object's id property valueassertionMethod.publicKeyMultibase property equal to a string of base58btc-alphabet characters, i.e. matching the regular expression [1-9A-HJ-NP-Za-km-z]+These are test cases for this test case, and can be used to verify that an implementation of this test case specification will be consistent with other implementations.
"},{"location":"fep/521a/fep-521a-test-case/#missing-assertionmethod","title":"Missing assertionMethod","text":"input
actor:
{\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\"\n}\n test result
inapplicableinput
actor:
{\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"assertionMethod\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n}\n test targets
inapplicable, warning (malformed assertionMethod array)input
actor:
{\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"assertionMethod\": [{\n \"inappropriateKey\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n }]\n}\n test targets
inapplicable, warning (malformed assertionMethod member)input
actor:
{\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"assertionMethod\": {[\n \"id\": \"https://example.com/#ed25519-key\",\n \"type\": \"Multikey\",\n \"controller\": \"https://example.com/\",\n \"publicKeyMultibase\": \"6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n ]}\n}\n test targets
inapplicable, warning (malformed assertionMethod member)input
actor:
{\n\"type\": \"Person\",\n\"inbox\": \"https://example.com/inbox\",\n\"outbox\": \"https://example.com/outbox\",\n\"id\": \"https://example.com/\",\n\"assertionMethod\": [\n {\n \"id\": \"https://example.com/#ed25519-key\",\n \"type\": \"Multikey\",\n \"controller\": \"https://https://example.com/\",\n \"publicKeyMultibase\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n },\n {\n \"inappropriateKey\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n }\n]\n}\n test targets
assertionMethod[0]passedassertionMethod[1]inapplicable, warning (non-conformant entries)outcome","text":"An outcome is a conclusion that comes from evaluating a test on a test subject. An outcome can be one of the three following types:
inapplicable: No part of the test subject matches the applicabilitypassed: A test target meets all expectationsfailed: A test target does not meet all expectationsassertionMethod has outcome passed, requirement is satisfiedassertionMethod has outcome failed, requirement is not satisfiedassertionMethod has outcome inapplicable, further testing is needed to determine whether this requirement is satisfiedWITHDRAWN 2022-08-23 2025-06-24 #18 Discussions codeberg"},{"location":"fep/5624/#summary","title":"Summary","text":"Sometimes, users may want to share an information or a story without inviting replies from outside their circles or from anyone at all. In particular, individuals may want to restrict who can reply to them in order to avoid \u201creply guys\u201d or limit outright harassment, while instutions may want to disable replies on their posts to provide information without having to deal with a moderation burden.
This can be broken into an advisory part advertising what sets of actors are expected to be able to reply, and a collaborative verification process where third-parties check with the actor being replied to that the reply is indeed allowed.
"},{"location":"fep/5624/#requirements","title":"Requirements","text":"The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].
In the remaining of this FEP, \u201cdistribution authority\u201d (or \u201cauthority\u201d for short) refers to an actor that controls the distribution and audience of replies. The purpose of this wording is to make this FEP applicable both for models where replies are first-class posts, and for \u201cpost and comments\u201d models where comments only exist in the context of a post and the post author decides who gets to see the comments. In the absence of extensions, the \u201cauthority\u201d is the author of the post being replied to.
"},{"location":"fep/5624/#declaring-a-reply-policy","title":"Declaring a reply policy","text":"In order to advertise who is allowed to reply to an object, an author MAY set the canReply (http://joinmastodon.org/ns#canReply) property on their objects. If set, this property MUST be an empty array or one or more actors or collections.
To ease implementation, collections SHOULD be restricted to one of the following: - as:Public, to indicate that anyone can reply - the authority's followers collection (if defined) - the authority's followed collection (if defined)
In addition, canReply SHOULD contain every actor mentioned in the original object.
Whenever one of these collections is used, the receiving end can easily know whether they are expected to be able to reply.
"},{"location":"fep/5624/#example-object","title":"Example object","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\": \"http://joinmastodon.org/ns#\",\n \"canReply\": \"toot:canReply\"\n }\n ],\n \"attributedTo\": \"https://example.com/users/1\",\n \"id\": \"https://example.com/users/1/statuses/1\",\n \"type\": \"Note\",\n \"content\": \"Hello world\",\n \"canReply\": \"https://www.w3.org/ns/activitystreams#Public\"\n}\n"},{"location":"fep/5624/#checking-whether-the-user-can-reply-and-submitting-the-reply-to-the-authority","title":"Checking whether the user can reply and submitting the reply to the authority","text":"When an object with canReply is set, it SHOULD be conveyed in human-readable form to the user if possible, for instance with something like \u201cOnly mentioned users can reply\u201d or \u201cOnly people Authority follows and mentioned users can reply\u201d.
The software SHOULD NOT offer the user to reply unless it is directly mentioned in the object's tag attribute or listed in canReply (either directly or through a collection), or canReply contains a collection for which the recipient cannot efficiently check the membership of the would-be replier.
After locally verifying that the replier should be allowed to reply, the replier's end SHOULD POST the Create activity for the reply to the authority's inbox only, and consider the reply to be pending approval.
When receiving a reply to an object with a canReply property, the authority decides whether the reply is acceptable.
If the reply is considered acceptable, the authority MUST reply with an ApproveReply (http://joinmastodon.org/ns#ApproveReply) activity with the object property set to the id of the reply object, and its inReplyTo property set to the object it is in reply to.
That ApproveReply activity SHOULD be publicly dereferenceable and MUST be dereferenceable by all parties allowed to see the original post. It MUST NOT embed its object nor its inReplyTo as to avoid possible information leaks.
Additionally, the authority MAY forward an accepted reply according to its own rules.
If the reply is considered unacceptable, the authority SHOULD reply with a RejectReply (http://joinmastodon.org/ns#RejectReply) activity. This activity MAY be publicly accessible, but this is not a requirement.
The reason for using the new activity types ApproveReply and RejectReply is to be explicit about the purpose of the approval, as one could imagine other kinds of approvals, and remaining implicit may cause ambiguities with other potential extensions.
ApproveReply activity","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\": \"http://joinmastodon.org/ns#\",\n \"ApproveReply\": \"toot:ApproveReply\"\n }\n ],\n \"actor\": \"https://example.com/users/1\",\n \"id\": \"https://example.com/reply_approvals/1\",\n \"type\": \"ApproveReply\",\n \"object\": \"https://example.org/users/bob/statuses/3\",\n \"inReplyTo\": \"https://example.com/users/1/statuses/1\"\n}\n"},{"location":"fep/5624/#example-rejectreply-activity","title":"Example RejectReply activity","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\": \"http://joinmastodon.org/ns#\",\n \"RejectReply\": \"toot:RejectReply\"\n }\n ],\n \"actor\": \"https://example.com/users/1\",\n \"id\": \"https://example.com/reply_approvals/1\",\n \"type\": \"RejectReply\",\n \"object\": \"https://example.org/users/bob/statuses/3\"\n}\n"},{"location":"fep/5624/#receiving-approval-and-distributing-the-reply","title":"Receiving approval and distributing the reply","text":"After sending the initial Create, the replier SHOULD wait for an ApproveReply activity such as described above.
Once the ApproveReply has been received, the replier SHOULD add a replyApproval (http://joinmastodon.org/ns#replyApproval) property to their reply object pointing to the ApproveReply activity they received, and then MAY send a Create activity with the modified object to its intended audience.
If it instead receives a RejectReply, the reply SHOULD be immediately deleted and the replier MAY be notified.
replyApproval","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\": \"http://joinmastodon.org/ns#\",\n \"canReply\": \"toot:canReply\",\n \"replyApproval\": \"toot:replyApproval\"\n }\n ],\n \"attributedTo\": \"https://example.com/users/1\",\n \"id\": \"https://example.org/users/bob/statuses/3\",\n \"type\": \"Note\",\n \"content\": \"@alice hello!\",\n \"inReplyTo\": \"https://example.com/users/1/statuses/1\",\n \"canReply\": \"https://www.w3.org/ns/activitystreams#Public\",\n \"replyApproval\": \"https://example.com/reply_approvals/1\",\n \"tag\": {\n \"type\": \"Mention\",\n \"href\": \"https://example.com/users/1\"\n }\n}\n"},{"location":"fep/5624/#verifying-third-party-replies","title":"Verifying third-party replies","text":"When processing a reply from a remote actor to a remote authority, a recipient SHOULD discard any reply that does not match any of the following conditions: - the object it is in reply to does not set a canReply property - the object has been received through the remote authority - the object it is in reply to has a canReply containing the as:Public collection - the author of the reply appears in a Mention object in the tag property of the object it is in reply to - the object it is in reply to sets a non-empty canReply, and replyApproval can be dereferenced and is a valid ApproveReply activity
To be considered valid, the ApproveReply activity referenced in replyApproval MUST satisfy the following properties: - its actor property is the authority - its authenticity can be asserted - its object property is the reply under consideration - its inReplyTo property matches that of the reply under consideration
In addition, if the reply is considered valid, but has no valid replyApproval despite the object it is in reply to having a canReply property, the recipient MAY hide the reply from certain views.
The authority may want to perform /a posteriori/ moderation of their replies.
To do this, the authority SHOULD send a RejectReply activity to the sender and the reply's audience, with the reply URI as the object property. The object property MUST NOT be embedded, as to avoid possible information leaks.
The URI at which the previously-offered ApproveReply was available should return HTTP 404 or redirect to the newly-issued RejectReply activity.
Upon receiving a RejectReply activity for a previously-accepted reply, third-parties SHOULD check that the RejectReply is valid and SHOULD delete or hide the revoked reply if it is.
To be considered valid, the RejectReply activity MUST satisfy the following properties: - its actor property is the authority - its authenticity can be asserted - its object property is the reply under consideration
Because it is unrealistic to expect all implementations and deployments to implement this proposal at the same time, deployment SHOULD be gradual, with verification of third-party replies only performed once the other steps are widely implemented. To encourage adoption without breaking compatibility altogether, implementations MAY want to hide non-validated replies from certain views (e.g. requiring a click to see \u201chidden replies\u201d, or not showing the reply to non-followers).
"},{"location":"fep/5624/#security-considerations","title":"Security considerations","text":"By not adding a hash or copy of the reply in the ApproveReply activity, malicious actors could exploit this in a split horizon setting, sending different versions of the same activity to different actors. This is, however, already a concern in pretty much all contexts in ActivityPub, and enshrining that information in the ApproveReply activity would have many drawbacks:
ApproveReply activity is publicly dereferenceableNone so far.
"},{"location":"fep/5624/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/5711/","title":"FEP-5711: Inverse Properties for Collections","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.orgDRAFT 2025-03-24 #539 Discussions codeberg"},{"location":"fep/5711/#summary","title":"Summary","text":"This FEP defines inverse properties for collections that are important in ActivityPub.
"},{"location":"fep/5711/#motivation","title":"Motivation","text":"The Activity Vocabulary defines a number of important collection properties for organizing reactions to an object. ActivityPub augments these with additional properties that specify the activity streams and social graph for actors.
Out of context, though, it can be hard to determine if a collection has a defined relationship to an object or actor. This can be important for clients that give users the ability to manipulate collections with the Add, Remove, Update and Delete activities, since the special collections defined in ActivityPub are usually maintained with different flows.
Other methods exist to identify collections that are the value of properties of actors. It's possible to use the attributedTo property to identify the actor, and then compare against all of the collection properties of the actor (inbox, outbox, and so on). This is a good fallback mechanism, but the inverse properties defined in this document are a more direct way to determine the relationship. There is no method for determining if a collection is the property of an object, however.
Additionally, one form of spoofing that a server could use is referring to collections as properties of objects that in fact are related to other objects. For example, a server could include a collection of likes as a property of an object that is not the object that the likes are for. This could be used to inflate the apparent popularity of an object. Inverse properties can help prevent this kind of spoofing by providing a two-way verification mechanism.
These inverse properties are most useful when the collections are represented separately from the object or actor they are related to. For example, this representation can help identify the object that the collection contains the likes of:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/b25kr36ief\",\n \"likesOf\": \"https://example.com/image/7pqs5i7c1e\"\n}\n This representation, on the other hand, is redundant, since the collection is represented as the property of the object:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Image\",\n \"id\": \"https://example.com/image/7pqs5i7c1e\",\n \"likes\": {\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/b25kr36ief\",\n \"likesOf\": \"https://example.com/image/7pqs5i7c1e\"\n }\n}\n"},{"location":"fep/5711/#user-stories","title":"User stories","text":"The following terms are defined in this vocabulary extension.
The examples in the following sections refer to the following example object:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Image\",\n \"id\": \"https://example.com/image/7pqs5i7c1e\",\n \"name\": \"A beautiful image\",\n \"url\": \"https://files.example.com/image/29m2p4kdq15.jpg\",\n \"likes\": \"https://example.com/collection/b25kr36ief\",\n \"shares\": \"https://example.com/collection/hvjgqhpv3e\",\n \"replies\": \"https://example.com/collection/u9hwmfih7r\"\n}\n ...and/or the following actor:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Person\",\n \"id\": \"https://example.com/person/bup9a8eqm\",\n \"name\": \"Alice\",\n \"inbox\": \"https://example.com/collection/tgiqwh6p3b\",\n \"outbox\": \"https://example.com/collection/hvf8crcd6r\",\n \"followers\": \"https://example.com/collection/x48dtnhlt9\",\n \"following\": \"https://example.com/collection/r9w7krxua\",\n \"liked\": \"https://example.com/collection/jzc50wc28l\"\n}\n"},{"location":"fep/5711/#likesof","title":"likesOf","text":"https://w3id.org/fep/5711#likesOfas:Collectionas:ObjectDefines an object for which the collection is the value of the likes property.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/b25kr36ief\",\n \"likesOf\": \"https://example.com/image/7pqs5i7c1e\"\n}\n"},{"location":"fep/5711/#sharesof","title":"sharesOf","text":"https://w3id.org/fep/5711#sharesOfas:Collectionas:ObjectDefines an object for which the collection is the value of the shares property.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/hvjgqhpv3e\",\n \"sharesOf\": \"https://example.com/image/7pqs5i7c1e\"\n}\n"},{"location":"fep/5711/#repliesof","title":"repliesOf","text":"https://w3id.org/fep/5711#repliesOfas:Collectionas:ObjectDefines an object for which the collection is the value of the replies property.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"OrderedCollection\",\n \"id\": \"https://example.com/collection/u9hwmfih7r\",\n \"repliesOf\": \"https://example.com/image/7pqs5i7c1e\"\n}\n"},{"location":"fep/5711/#inboxof","title":"inboxOf","text":"https://w3id.org/fep/5711#inboxOfas:Collectionas:Object (an ActivityPub actor)Defines an actor for which the collection is the value of the inbox property.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/tgiqwh6p3b\",\n \"inboxOf\": \"https://example.com/person/bup9a8eqm\"\n}\n"},{"location":"fep/5711/#outboxof","title":"outboxOf","text":"https://w3id.org/fep/5711#outboxOfas:Collectionas:Object (an ActivityPub actor)Defines an actor for which the collection is the value of the outbox property.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/hvf8crcd6r\",\n \"outboxOf\": \"https://example.com/person/bup9a8eqm\"\n}\n"},{"location":"fep/5711/#followersof","title":"followersOf","text":"https://w3id.org/fep/5711#followersOfas:Collectionas:Object (an ActivityPub actor)Defines an actor for which the collection is the value of the followers property.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/x48dtnhlt9\",\n \"followersOf\": \"https://example.com/person/bup9a8eqm\"\n}\n"},{"location":"fep/5711/#followingof","title":"followingOf","text":"https://w3id.org/fep/5711#followingOfas:Collectionas:Object (an ActivityPub actor)Defines an actor for which the collection is the value of the following property.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/r9w7krxua\",\n \"followingOf\": \"https://example.com/person/bup9a8eqm\"\n}\n"},{"location":"fep/5711/#likedof","title":"likedOf","text":"https://w3id.org/fep/5711#likedOfas:Collectionas:Object (an ActivityPub actor)Defines an actor for which the collection is the value of the liked property.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/jzc50wc28l\",\n \"likedOf\": \"https://example.com/person/bup9a8eqm\"\n}\n"},{"location":"fep/5711/#implementation-notes","title":"Implementation notes","text":"There should be only one inverse relationship property for a collection. There are conceivable structures where the replies collection of an object is also the liked collection of an actor, but these are rare.
All of these properties are functional, meaning that there is only one value for each collection.
"},{"location":"fep/5711/#security-considerations","title":"Security considerations","text":"Claims made by these properties should be verified by retrieving the referenced object or actor and checking the appropriate property. For example, if a server claims that a collection is the shares property of an object using the sharesOf property, the consumer should retrieve the referenced object and check that the shares property references the same collection.
Use of these inverse properties can help prevent spoofing of collections, but they do not provide verification of the contents of the collection. For example, a collection that uses the likesOf property to claim that it is the likes collection of a video, and which is in turn referenced by the video, may still contain Like activities that are not actually likes of the video.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/5bf0/","title":"FEP-5bf0: Collection sorting and filtering","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Michael Puckett michael@puckett.contactWITHDRAWN 2023-04-10 2025-06-14 #80 - codeberg"},{"location":"fep/5bf0/#summary","title":"Summary","text":"This proposal would allow Collections to have a streams property, as Actors do. The streams would be of the type CollectionView, a proposed vocabulary extension that represents a sorted and/or filtered version of a Collection. ActivityPub clients could then render CollectionViews without having to perform such filtering or sorting operations themselves.
Metadata about how the sorting or filtering has been applied would be applied using new proposed vocabulary extensions that leverage SHACL for describing constraints.
"},{"location":"fep/5bf0/#motivations","title":"Motivations","text":"Some ActivityPub clients rely only on C2S protocols for accessing Collections and displaying the nested items.
Currently, in order to support filtering or sorting, these clients need to retrieve all paginated items, assemble them, manually sort or filter them, and then re-paginate them.
This proposal would allow servers to perform these kinds of operations, either at runtime or ahead of time, to ease the burden on clients.
Other servers should be free to explore the Collections, but they can be easily ignored, along with the new properties.
"},{"location":"fep/5bf0/#implementation","title":"Implementation","text":"A CollectionView extends from OrderedCollection and represents a filtered and/or sorted version of a Collection. Similarly, a CollectionViewPage extends from OrderedCollectionPage.
The method of filtering applied to the CollectionView can be indicated via its \"filter\" property, which maps to one or more SHACL Shapes.
The vocabulary would also provide a SHACL Shape for indicating that a property's value is among the items in a particular ActivityStreams Collection.
The method of sorting applied to the CollectionView can be indicated via its \"sort\" property, which maps to a SortShape. A SortShape extends SHACL's PropertyShape and adds the \"order\" property which can be mapped to \"Ascending\" or \"Descending\".
If there is no \"sort\" property, the order is the same as that of the original Collection.
"},{"location":"fep/5bf0/#examples","title":"Examples","text":"Here, an Actor's Inbox returns all Activities posted by the Actor, and the server also provides filtered versions as streams for client consumption.
The first CollectionView returns only the Like Activities.
The second CollectionView returns only created Articles with replies, demonstrating multiple filters and filtering on nested properties.
The third CollectionView demonstrates how to indicates that a given property's value is in a partiular ActivityStreams Collection. In the example, the CollectionView is returning Activities by Alyssa's Co-workers. (Alyssa has a custom stream of mutual followers who she has labeled as Co-workers.)
{\n \"@context\": {\n \"@vocab\": \"https://www.w3.org/ns/activitystreams#\",\n \"fep\": \"https://w3id.org/fep#\",\n \"CollectionView\": \"fep:CollectionView\",\n \"viewOf\": \"fep:viewOf\",\n \"filter\": \"fep:filter\",\n \"sort\": \"fep:sort\",\n \"inCollection\": \"fep:inCollection\",\n \"SortShape\": \"fep:SortShape\",\n \"order\": \"fep:order\",\n \"sh\": \"http://www.w3.org/ns/shacl#\",\n \"PropertyShape\": \"sh:PropertyShape\",\n \"path\": \"sh:path\",\n \"hasValue\": \"sh:hasValue\",\n \"minCount\": \"sh:minCount\"\n },\n \"id\": \"https://example.social/@alyssa/inbox\",\n \"type\": \"OrderedCollection\",\n \"name\": \"Inbox\",\n \"totalItems\": 1000,\n \"first\": \"https://example.social/@alyssa/inbox/page/1\",\n \"last\": \"https://example.social/@alyssa/inbox/page/2\",\n \"streams\": [\n {\n \"id\": \"https://example.social/@alyssa/inbox/likes\",\n \"type\": \"CollectionView\",\n \"name\": \"Likes\",\n \"filter\": {\n \"type\": \"PropertyShape\",\n \"path\": \"type\",\n \"hasValue\": \"Like\"\n },\n \"sort\": {\n \"type\": \"SortShape\",\n \"path\": \"published\",\n \"order\": \"Descending\"\n },\n \"totalItems\": 10,\n \"first\": \"https://example.social/@alyssa/inbox/likes/page/1\",\n \"last\": \"https://example.social/@alyssa/inbox/likes/page/1\"\n },\n {\n \"id\": \"https://example.social/@alyssa/inbox/posts-with-replies\",\n \"type\": \"CollectionView\",\n \"name\": \"Posts with Replies\",\n \"filter\": [\n {\n \"type\": \"PropertyShape\",\n \"path\": \"type\",\n \"hasValue\": \"Create\"\n },\n {\n \"type\": \"PropertyShape\",\n \"path\": [\"object\", \"inReplyTo\"],\n \"minCount\": 1\n }\n ],\n \"sort\": {\n \"type\": \"SortShape\",\n \"path\": \"published\",\n \"order\": \"Descending\"\n },\n \"totalItems\": 10,\n \"first\": \"https://example.social/@alyssa/inbox/blog-posts/page/1\",\n \"last\": \"https://example.social/@alyssa/inbox/blog-posts/page/1\",\n \"viewOf\": \"https://example.social/@alyssa/inbox\"\n },\n {\n \"id\": \"https://example.social/@alyssa/inbox/notes-by-coworkers\",\n \"type\": \"CollectionView\",\n \"name\": \"Posts by Co-Workers\",\n \"filter\": {\n \"type\": \"InCollectionShape\",\n \"path\": \"actor\",\n \"inCollection\": \"https://example.social/@alyssa/friends/coworkers\"\n },\n \"sort\": {\n \"type\": \"SortShape\",\n \"path\": \"published\",\n \"order\": \"Descending\"\n },\n \"totalItems\": 10,\n \"first\": \"https://example.social/@alyssa/inbox/notes-by-coworkers/page/1\",\n \"last\": \"https://example.social/@alyssa/inbox/notes-by-coworkers/page/1\",\n \"viewOf\": \"https://example.social/@alyssa/inbox\"\n }\n ]\n}\n"},{"location":"fep/5bf0/#vocabulary-extensions","title":"Vocabulary Extensions","text":"Here are the terms that would needed to be added to the FEP vocabulary:
{\n \"@context\": {\n \"fep\": \"https://w3id.org/fep#\",\n \"as\": \"https://www.w3.org/ns/activitystreams#\",\n \"sh\": \"http://www.w3.org/ns/shacl#\",\n \"rdf\": \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\",\n \"rdfs\": \"http://www.w3.org/2000/01/rdf-schema#\"\n },\n \"fep:CollectionView\": {\n \"@id\": \"fep:CollectionView\",\n \"@type\": \"rdfs:Class\",\n \"rdfs:subClassOf\": \"as:OrderedCollection\",\n \"rdfs:label\": \"Collection View\",\n \"rdfs:comment\": \"Represents a sorted and/or filtered version of a Collection\"\n },\n \"fep:CollectionViewPage\": {\n \"@id\": \"fep:CollectionViewPage\",\n \"@type\": \"rdfs:Class\",\n \"rdfs:subClassOf\": \"as:OrderedCollectionPage\",\n \"rdfs:label\": \"Collection View Page\",\n \"rdfs:comment\": \"Represents a sorted and/or filtered version of a CollectionPage\"\n },\n \"fep:SortShape\": {\n \"@id\": \"fep:SortShape\",\n \"@type\": \"rdfs:Class\",\n \"rdfs:subClassOf\": \"sh:PropertyShape\",\n \"rdfs:label\": \"Sort Shape\",\n \"rdfs:comment\": \"A PropertyShape with an 'order' property\"\n },\n \"fep:filter\": {\n \"@id\": \"fep:filter\",\n \"@type\": \"rdf:Property\",\n \"rdfs:domain\": \"fep:CollectionView\",\n \"rdfs:range\": \"sh:Shape\",\n \"rdfs:label\": \"Filter\",\n \"rdfs:comment\": \"The method of filtering applied to the CollectionView\"\n },\n \"fep:sort\": {\n \"@id\": \"fep:sort\",\n \"@type\": \"rdf:Property\",\n \"rdfs:domain\": \"fep:CollectionView\",\n \"rdfs:range\": \"fep:SortShape\",\n \"rdfs:label\": \"Sort\",\n \"rdfs:comment\": \"The method of sorting applied to the CollectionView\"\n },\n \"fep:order\": {\n \"@id\": \"fep:order\",\n \"@type\": \"rdf:Property\",\n \"rdfs:domain\": \"fep:SortShape\",\n \"rdfs:range\": \"fep:SortOrderType\",\n \"rdfs:label\": \"Order\",\n \"rdfs:comment\": \"Indicates whether the sort order is 'Ascending' or 'Descending'\"\n },\n \"fep:SortOrderType\": {\n \"@id\": \"fep:SortOrderType\",\n \"@type\": \"rdfs:Class\",\n \"rdfs:label\": \"Sort Order Type\",\n \"rdfs:comment\": \"For indicating the sort order\"\n },\n \"fep:Ascending\": {\n \"@id\": \"fep:Ascending\",\n \"@type\": \"fep:SortOrderType\",\n \"rdfs:label\": \"Ascending\",\n \"rdfs:comment\": \"Indicates ascending sort order\"\n },\n \"fep:Descending\": {\n \"@id\": \"fep:Descending\",\n \"@type\": \"fep:SortOrderType\",\n \"rdfs:label\": \"Descending\",\n \"rdfs:comment\": \"Indicates descending sort order\"\n },\n \"fep:InCollectionShape\": {\n \"@id\": \"fep:InCollectionShape\",\n \"@type\": \"rdfs:Class\",\n \"rdfs:subClassOf\": \"sh:PropertyShape\",\n \"rdfs:label\": \"In Collection Shape\",\n \"rdfs:comment\": \"For filtering on whether a property's value is in a Collection.\",\n \"sh:js\": \"fep:inCollectionFunction\"\n },\n \"fep:inCollection\": {\n \"@id\": \"fep:inCollection\",\n \"@type\": \"rdf:Property\",\n \"rdfs:domain\": \"fep:InCollectionShape\",\n \"rdfs:range\": \"sh:IRI\",\n \"rdfs:label\": \"In Collection\",\n \"rdfs:comment\": \"Maps a Collection URL to fep:inCollectionFunction\"\n },\n \"fep:inCollectionFunction\": {\n \"@id\": \"fep:inCollectionFunction\",\n \"@type\": \"sh:JSFunction\",\n \"sh:jsFunction\": \"inCollection\",\n \"sh:jsLibrary\": \"https://w3id.org/fep/functions.js\",\n \"sh:parameter\": {\n \"@type\": \"sh:Parameter\",\n \"sh:path\": \"fep:inCollection\"\n }\n },\n \"fep:viewOf\": {\n \"@id\": \"fep:viewOf\",\n \"@type\": \"rdf:Property\",\n \"rdfs:label\": \"View of\",\n \"rdfs:domain\": \"fep:CollectionView\",\n \"rdfs:range\": \"as:Collection\",\n \"rdfs:comment\": \"A reference back to the original Collection\"\n }\n}\n"},{"location":"fep/5bf0/#security","title":"Security","text":"Servers could in theory make available a templated URL endpoint that allows for arbitrary sorting or filtering. This should be discouraged, as it could lead to database injections. Instead, only predetermined sorted/filtered CollectionViews should be made available via the streams property.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/5e53/","title":"FEP-5e53: Opt-out Preference Signals","text":"Authors Status Date received Tracking issue Discussions Repository Don Marti dmarti@zgp.orgDRAFT 2024-06-09 #327 Discussions codeberg"},{"location":"fep/5e53/#summary","title":"Summary","text":"Some users have concerns about how their content and/or personal information are used. For example, some users do not want the content they created to be used for training generative AI systems, and some users do not want to have their personal information shared or sold.
Several opt-out preference signals (OOPSs) have been standardized or proposed in the form of HTTP headers that can apply to a connection between a user and a central server. In some jurisdictions, companies that administer web sites are required to process and act on OOPSs.
This FEP extends ActivityPub to support passing OOPSs along with the content and user information to which they may apply. This FEP refers to existing OOPSs that have already been documented, and does not propose new ones.
"},{"location":"fep/5e53/#declaring-an-oops","title":"Declaring an OOPS","text":"In order to apply an OOPS to an object, an author MAY set the JSON-LD properties xRobotsTag and/or SPC. For example,
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://fep.example/ns/privacyHeaders\"],\n \"type\": \"Note\",\n \"content\": \"Don't surveil me bro\",\n \"xRobotsTag\": \"noai\",\n \"SPC\": 1\n}\n A recipient SHOULD process the content and metadata of the object as if the recipient had received it over an HTTP connection with the corresponding header.
SPC: process as if the object has been received in an HTTP connection with the Sec-GPC HTTP request header.
xRobotsTag: process as if the object had been received with an X-Robots-Tag HTTP response header with a value equal to this property.
The existing Global Privacy Control (GPC) standard allows for individuals in an increasing number of jurisdictions to pass a legally binding opt-out preference signal indicating a commonly held privacy norm. However, GPC is implemented as an HTTP request header, which makes it only works from client to server. This limits the ability of users to pass a privacy opt-out in a federated communications medium. A federated system where a user's information may be passed from one server to another is currently at a disadvantage in serving users who wish to opt out, because the opt-out preference signal does not travel with the information to which it applies. SPC is intended to have the same effects as GPC, but for cases where the user does not have a direct HTTP connection to the recipient of some personal information. Adding the SPC opt-out to ActivityPub would bring parity with centralized social sites.
Fediverse instances should make SPC configurable by users, and reflect a user's actual intent to opt out. Fediverse instances should detect when a user has turned on GPC or some other privacy opt-out or setting, and offer to apply SPC to that user's objects.
Content sharing platforms that operate in a centralized manner are now frequently promoting their support for signaling opt-outs to generative AI training, using the noai value for the X-Robots-Tag header. See RobotsMeta and RobotExclusion. In order for federated content sharing systems to provide an attractive alternative to centralized ones, federated systems will likely need to also offer an opt out signaling feature.
"},{"location":"fep/5e53/#security-considerations","title":"Security considerations","text":"This proposal depends on compliant processing of OOPSs by all actors.
Some jurisdictions require privacy OOPSs to be registered in order to have legal effect. In the event this FEP is accepted, the author intends to pursue registration.
"},{"location":"fep/5e53/#implementations","title":"Implementations","text":"None so far.
"},{"location":"fep/5e53/#related-proposal","title":"Related proposal","text":"FEP-c118 suggests establishing a content licensing framework. This may be possible by adapting this FEP to include the license header from WebLinking.
"},{"location":"fep/5e53/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/5feb/","title":"FEP-5feb: Search indexing consent for actors","text":"Authors Status Date received Tracking issue Discussions Repository Claire claire.fep-1d7d@sitedethib.comDRAFT 2023-09-06 #154 - codeberg"},{"location":"fep/5feb/#summary","title":"Summary","text":"This FEP introduces an actor-level attribute that can be used to explicitly express an actor's consent (or lack thereof) to their public objects being indexed for search purposes.
Akin to robots.txt and noindex meta tags, this attribute is advisory and relies on the indexers respecting the directive, as public objects can not technically be prevented from being indexed.
The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].
"},{"location":"fep/5feb/#specifying-search-indexing-consent-at-the-actor-level","title":"Specifying search indexing consent at the actor-level","text":"Actors can use the indexable (http://joinmastodon.org/ns#indexable) attribute to specify whether they consent to their public objects being indexed for search or not.
A missing indexable attribute SHOULD be handled as indexable: false.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\":\"http://joinmastodon.org/ns#\",\n \"indexable\":\"toot:indexable\",\n }\n ],\n \"id\": \"https://example.com/users/1\",\n \"type\":\"Person\",\n \"inbox\": \"https://example.com/users/1/inbox\",\n \"outbox\": \"https://example.com/users/1/outbox\",\n \"preferredUsername\": \"bob\",\n \"indexable\": true\n}\n"},{"location":"fep/5feb/#searchable-objects","title":"Searchable objects","text":"Objects authored by an actor with indexable: true and addressed to the as:Public special collection SHOULD be made available for search.
Objects authored by an actor with indexable: false MUST NOT be made available for search to other users unless they were previously displayed and interacted with (e.g. it is allowed for a user to search posts they have bookmarked).
indexable attribute","text":"Whenever an actor is updated and its attribute is set to indexable: true, its objects SHOULD be made available for search as described in the previous section.
Whenever an actor is updated and its attribute is set to indexable: false, its objects MUST be removed from search as described in the previous section.
Considering this attribute is purely advisory, special care SHOULD be given to the user interface to make sure users are not made to believe the attribute will ensure they never get indexed.
"},{"location":"fep/5feb/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/612d/","title":"FEP-612d: Identifying ActivityPub Objects through DNS","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Helge helge.krueger@gmail.comWITHDRAWN 2023-04-18 2025-10-10 #89 - codeberg"},{"location":"fep/612d/#summary","title":"Summary","text":"In [ActivityPub], objects are identified through their id, which is a dereferenciable URI. For this, one adds a TXT record to DNS with name _apobjid and value corresponding to the URI of the ActivityPub object. If a domain name is then passed to a FediVerse application, it can then perform the DNS lookup, and resolve it to the ActivityPub object.
This FEP is inspired by [BlueSky], and it gets the FediVerse half of the way there. It allows one to look up the ActivityPub Actor from an associated domain name, it does not address the issue of diplaying the domain name instead of the FediVerse handle. One first step would be to include the domain name in alsoKnownAs. Then use some property of the actor to specify, which object to display.
"},{"location":"fep/612d/#other-mechanisms","title":"Other Mechanisms","text":"Two other mechanisms to identify ActivityPub objects beside their id are currently in use:
"},{"location":"fep/612d/#webfinger","title":"Webfinger","text":"The FediVerse handle username@domain.tld is commonly used as an identifier for users on the FediVerse. This handle can be resolved by performing a Webfinger lookup via
GET https://domain.tld/.well-known/webfinger?resource=acct:username@domain.tld\n the activity pub object is then contained in the link with type application/activity+json.
The second method, that is less commonly used, is to provide a HTML link header of the form
<link\n href=\"https://activty.pub/object/id\"\n rel=\"alternate\"\n type=\"application/activity+json\"\n/>\n A FediVerse object receiving this HTML as a response to a lookup, can then parse it and use the link to lookup the corresponding object.
"},{"location":"fep/612d/#content-negotiation","title":"Content Negotiation","text":"On a server supporting content negotiation, a request with Accept header \"application/activity+json\" will be either served or redirected to the corresponding ActivityPub object. The author does not a method to identify where the ActivityPub object is, as it is being directly served.
"},{"location":"fep/612d/#example-of-using-dns-to-identify-an-activitypub-object","title":"Example of using DNS to identify an ActivityPub object","text":"The following shows an example configuration for the domain mymath.rocks correspond to the actor with FediVerse handle @helge@mymath.rocks, i.e. the author,
$ dig _apobjid.mymath.rocks\n\n;; ANSWER SECTION:\n_apobjid.mymath.rocks. 7200 IN TXT \"https://mymath.rocks/endpoints/SYn3cl_N4HAPfPHgo2x37XunLEmhV9LnxCggcYwyec0\"\n The corresponding object being
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\"],\n \"id\":\"https://mymath.rocks/endpoints/SYn3cl_N4HAPfPHgo2x37XunLEmhV9LnxCggcYwyec0\",\n \"name\":\"Helge\",\n \"preferredUsername\":\"helge\",\n \"summary\":\"<p>I like Math, cows, and wrote <a href=\\\"https://codeberg.org/bovine/bovine/\\\">bovine</a>.</p>\",\n \"type\":\"Person\", ...\n}\n"},{"location":"fep/612d/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/61cf/","title":"Index","text":"hide: - navigation description: \"OpenWebAuth is the \"single sign-on\" mechanism used by Hubzilla, (streams) and other related projects. It allows a browser-based user to log in to services across the Fediverse using a single identity. Once logged in, they can be recognised by other OpenWebAuth-compatible services, without third-party cookies and often without any explicit user interaction. This is not a specification, a proposal, or a \"best practice\" document. The aim is to describe the existing protocol in detail as an aid to implementers, evaluators, and anyone who wants to understand its operation. It is mostly based on reverse-engineering the existing implementations and focuses on the minimal requirements for basic interoperability. In OpenWebAuth, each user is identified by a public/private key pair. The protocol relies on there being a mechanism for other nodes on the network to discover a user's public key. This document assumes that ActivityPub actor objects will be used for this purpose. OpenWebAuth can also work with other protocols such as Zot6 and Nomad but these are not considered here.\"
"},{"location":"fep/61cf/#fep-61cf-the-openwebauth-protocol","title":"FEP-61cf: The OpenWebAuth Protocol","text":"Authors Status Type Date received Tracking issue Discussions Repository FenTiger @FenTiger@zotum.netDRAFT implementation 2024-02-06 #263 - codeberg OpenWebAuth is a federated remote authentication protocol. It can be used alongside protocols such as ActivityPub and Zot to add single sign-on to the Fediverse or to other \"social web\" projects such as blogs.
"},{"location":"fep/61cf/#summary","title":"Summary","text":"OpenWebAuth is the \"single sign-on\" mechanism used by Hubzilla, (streams) and other related projects. It allows a browser-based user to log in to services across the Fediverse using a single identity. Once logged in, they can be recognised by other OpenWebAuth-compatible services, without third-party cookies and often without any explicit user interaction.
This is not a specification, a proposal, or a \"best practice\" document. The aim is to describe the existing protocol in detail as an aid to implementers, evaluators, and anyone who wants to understand its operation. It is mostly based on reverse-engineering the existing implementations and focuses on the minimal requirements for basic interoperability.
In OpenWebAuth, each user is identified by a public/private key pair. The protocol relies on there being a mechanism for other nodes on the network to discover a user's public key. This document assumes that ActivityPub actor objects will be used for this purpose. OpenWebAuth can also work with other protocols such as Zot6 and Nomad but these are not considered here.
"},{"location":"fep/61cf/#overview","title":"Overview","text":"The protocol takes place between two participants:
The home instance, which hosts the user's identity, and is equivalent to the Identity Provider (IdP) in SAML and OpenID Connect.
The target instance, which allows remote users to log in to it using the provided identity. This is equivalent to the Relying Party (RP) in SAML and OpenID Connect.
The OpenWebAuth login flow can begin in one of two ways:
The user visits the target instance and sees a login screen. They type their Fediverse ID into a form field and click \"Login\".
The user follows a link to the target instance. This link has a query parameter, zid=, which specifies the user's Fediverse ID.
This zid= mechanism is not necessarily specific to OpenWebAuth. It can be used to start an OAuth2-based login flow in just the same way. Implementers should note, though, that it can make attacks easier to launch; an attacker who can trick a victim into clicking a malicious link can cause them to start the login flow using a maliciously constructed identity, perhaps leading to a MixUpAttack.
The protocol can be summarised in a sequence diagram as below. This diagram shows the zid= case, where the user is not prompted to log in.
sequenceDiagram\n participant browser as Browser\n participant target as Target instance\n participant home as Home instance\n\n browser ->> target: GET /page?zid=user@home\n target ->> home: webfinger user@home\n home -->> target: location of redirection endpoint\n target -->> browser: Location: https://home.example/magic?...\n browser ->> home: GET /magic?...\n Note over home: Check user is logged in,<br/>eg by checking session cookie\n home ->> target: webfinger /\n target -->> home: location of token endpoint\n rect rgb(216, 255, 216)\n Note over home,target: Protected by actor's private key\n home ->> target: GET /token<br/>(signed)\n target -->> home: <token><br/>(encrypted)\n end\n home -->> browser: Location: https://target.example/page?owt=<token>\n browser ->> target: GET /page?owt=<token>\n target -->> browser: <contents of page> Regardless of how the flow is started, the protocol begins with the user's browser making a request to the target instance.
"},{"location":"fep/61cf/#1-redirection-to-home-instance","title":"1. Redirection to home instance","text":"First the target instance identifies the home instance's \"redirection endpoint\".
Some existing implementations hard-code this to /magic. New implementations should perform a webfinger lookup on the provided user ID looking for a link with rel set to http://purl.org/openwebauth/v1#redirect. If found, this link's href should be used as the redirection endpoint.
The target instance constructs a URL from the redirection endpoint with the following query parameters: - owa: must be set to 1 - bdest: The URL which the browser will be returned to after acquiring a token. This is encoded as UTF-8 and then converted to a hexadecimal string. This is equivalent to the redirect_uri in OAuth2. The bdest URL can include query parameters.
The user's browser is redirected to this URL. The target instance should check that the URL has the same origin as the webfinger ID, to avoid acting as an open redirector.
"},{"location":"fep/61cf/#2-home-instance-requests-a-token","title":"2. Home instance requests a token","text":"The /magic endpoint at the user's home instance first checks that the user's browser has a valid session cookie.
If so, it decodes the bdest destination URL. It performs a webfinger lookup on the root URL of the destination site and looks for a link with rel set to http://purl.org/openwebauth/v1. This identifies the target instance's \"token endpoint\".
If an error occurs during this step, the home instance should not redirect to the 'bdest' URL; this would allow it to be used as an open redirector. Instead it should respond with a suitable HTTP error code.
On success, the home instance constructs and issues a signed HTTPS request to the discovered token endpoint. The request also contains an additional signed header, X-Open-Web-Auth, containing a random string. Target instances do not use this header; it is provided to add additional entropy to the signature calculation.
The target instance's token endpoint extracts the keyId, fetches the actor record, extracts the public key and verifies the signature.
On success, it generates an URL-safe random string to use as a token. This token is stored locally, associated with the actor who signed the message. The token is also encrypted using the actor's public key and the RSA PKCS #1 v1.5 encryption scheme. The encrypted result is encoded as URL-safe Base64 with no '=' padding bytes.
Next it constructs the following JSON object in response:
{\n \"success\": true,\n \"encrypted_token\": <the base64-encoded token>\n}\n On failure it can also return a result with success set to false.
The signed request issued in step 2 completes. The home instance decodes the JSON response and verifies that success is true. Next it decodes the Base64-encoded encrypted token and decrypts it using the actor's private key.
If successful, it takes the bdest destination URL, adds the query parameter: owt=<decrypted token>, and redirects the user's browser to it.
The user arrives back at the target instance. The target instance sees the owt= query parameter and checks its local storage for the token which it saved in step 3.
If found, this token identifies the remote user, and the target instance logs them in, overriding any existing login they may have. The token is also deleted from local storage so that it cannot be redeemed more than once.
"},{"location":"fep/61cf/#what-next","title":"What Next?","text":"(This section is non-normative.)
What does the target instance allow the user to do, now that they have signed in? The simplest option is to treat them as though they had signed in using a local username and password, but this isn't really enough.
In an ActivityPub context, a remotely authenticated user will expect to be able to post to the target instance using their existing identity. This poses the question of how the target instance should sign the post, since it does not have access to the user's private key. Hubzilla solves this by attributing the post to the user's identity, but signing it with a key belonging to the target instance; any other instance which receives the post must be aware of this mechanism, or the signature will be rejected.
"},{"location":"fep/61cf/#additional-notes","title":"Additional notes","text":""},{"location":"fep/61cf/#http-signatures","title":"HTTP Signatures","text":"An OpenWebAuth signed request must have an Authorization header starting with the word Signature followed by the encoded HTTP signature. See ActPubSig for more details on signing Fediverse requests.
To support logged in users, the target instance needs some logic to identify their requests. Normally this is done by checking for a valid session cookie. To support OpenWebAuth this logic must be extended to also check for the zid= and owt= query parameters.
Some corner cases are possible here. For instance, the user could already be logged in to the target instance when the OWA login flow begins.
When the OpenWebAuth flow succeeds, the owt= token will identify the logged-in user. It is important that the target instance trusts this token rather than the original zid= query parameter, to prevent an impersonation attack.
This endpoint should accept both GET and POST requests. Some home instances will issue POSTs with random bodies.
"},{"location":"fep/61cf/#home-instances-magic-endpoint","title":"Home instance's/magic endpoint","text":"The implementation of this endpoint needs to request a login token from the target instance. This requires it to prove possession of the user's private key, first to calculate a signature for the request and then to decrypt the returned token. These are the only places in the protocol where the private key is needed, implying that only the home instance needs to be a Fediverse instance. The target instance only needs access to public keys, meaning that OpenWebAuth can be used to allow users to log into things that are not instances.
"},{"location":"fep/61cf/#implementations","title":"Implementations","text":"The purpose of OpenWebAuth is to provide a strong guarantee of a user's identity to the web sites that they visit. This is often considered undesirable and consideration should be given to preventing this information from leaking to sites which may not be acting in the user's best interests.
This consideration may involve policies such as displaying a consent screen to the user or otherwise allowing them to choose which target instances they are willing to authenticate themselves to. The user's browser is redirected to their home instance at step 2, giving it an opportunity to implement policies such as these.
"},{"location":"fep/61cf/#denial-of-service-attack","title":"Denial-of-service attack","text":"Unused owt= login tokens are deleted after a couple of minutes. This protects against a potential DoS attack which could fill up the target instance's storage with unused tokens.
Suppose Mallory is logged into his home instance as mallory@example.social. It is easy for him to manually navigate to a URL on Alice's instance which looks like https://alice.social/somepage?zid=bob@example.social. When the OpenWebAuth flow completes, Mallory will return to Alice's instance with an owt= token identifying him as mallory@example.social. It is important that Alice's instance trusts this owt= token. If it mistakenly trusts the original zid= parameter, Mallory will be able to impersonate Bob.
OpenWebAuth involves redirecting the user's browser twice. It is important to verify the relevant URLs to avoid an OpenRedirectionAttack.
The first redirection sends the user to their home instance's redirection endpoint (often /magic). If this endpoint is discovered via webfinger, the target instance should check that its hostname portion matches the hostname in the user's claimed ID.
The second redirection sends the user back to the URL specified in the bdest query parameter. This redirection should only take place if * the token endpoint discovery step succeeded * the discovered token endpoint URL has the same origin as the bdest URL
In any other case the home instance's redirection endpoint should return a suitable HTTP error code.
"},{"location":"fep/61cf/#mixup-attack","title":"Mixup attack","text":"The zid= query parameter may make a MixUpAttack easier to launch, as discussed briefly above. This does not apply to OpenWebAuth but should be considered carefully when trying to generalise the zid= mechanism to other authentication protocols.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/6481/","title":"FEP-6481: Specifying ActivityPub extension support with NodeInfo","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository James Smith james@floppy.org.ukWITHDRAWN 2024-03-12 2024-10-31 #279 - codeberg"},{"location":"fep/6481/#summary","title":"Summary","text":"Many FediVerse services extend ActivityPub and ActivityStreams to add their own behaviour, such as custom object types. In order to interoperate with other servers running different software, the service needs to know whether or not the remote server supports these same extensions.
This FEP defines a standard method of specifying support for particular extensions in the server's NodeInfo file (as described in FEP-f1d5), so that compatibility information can be automatically discovered.
"},{"location":"fep/6481/#history","title":"History","text":"Many FediVerse software platforms such as Bookwyrm and Pixelfed have used the NodeInfo software field to determine if a remote server is running the same software, and is therefore compatible with their extensions. This works, however it restricts full interoperability with different software that could potentially use the same types.
The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in RFC-2119.
"},{"location":"fep/6481/#declaring-extension-support","title":"Declaring extension support","text":"FediVerse software that extends ActivityPub SHOULD indicate extension support in its NodeInfo file.
Extension identifiers MUST be valid IRIs. IRIs MAY be created with the w3id.org Permanent Identifier service, and if so they SHOULD redirect to a document that describes the extension.
To comply with this FEP, extensions MUST be specified in the metadata section of the NodeInfo file, as shown below:
{\n \"version\": \"2.2\",\n ...,\n \"protocols\": [\"activitypub\"],\n ...,\n \"metadata\": {\n \"activitypub\": {\n \"extensions\": [\n \"https://w3id.org/my-project/my-extension#v1\"\n ]\n }\n }\n}\n Note: The metadata key activitypub is chosen to match the string in protocols, and is refers to extensions in any of ActivityPub, ActivityStreams and the Activity Vocabulary.
It is RECOMMENDED that extension developers specify version information in their IRIs, for instance in a fragment identifier as above.
Once defined, IRIs MUST be constant - they cannot be changed while retaining compatibility. For this reason, it is RECOMMENDED to use a long-term service (e.g. w3id.org) for IRIs.
"},{"location":"fep/6481/#defining-iris","title":"Defining IRIs","text":"No specific recommendation is made for IRI design, although if using w3id.org, their Naming Policy should be adhered to.
"},{"location":"fep/6481/#examples","title":"Examples","text":"This section is non-normative.
Manyfold intends to define a new Activity Vocabulary Object type for its own rich content, 3dModel. The specification will define an extension IRI, for instance https://w3id.org/manyfold/3dModel#v1.
Applications that wish to receive Activities with this type can state their support for the extension in their NodeInfo file as follows; Manyfold instances will then know that they can send that object type and it will be understood.
{\n ...,\n \"metadata\": {\n \"activitypub\": {\n \"extensions\": [\n \"https://w3id.org/manyfold/3dModel#v1\"\n ]\n }\n }\n}\n A similar (entirely theoretical) example for Bookwyrm's Review object type might be: {\n ...,\n \"metadata\": {\n \"activitypub\": {\n \"extensions\": [\n \"https://joinbookwyrm.org/ns/activitypub#Review\"\n ]\n }\n }\n}\n"},{"location":"fep/6481/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/6606/","title":"FEP-6606: ActivityPub client to server collections addressing conventions","text":"Authors Status Date received Tracking issue Discussions Repository Marius Orcsik marius@federated.idDRAFT 2024-12-04 #453 Discussions codeberg"},{"location":"fep/6606/#summary","title":"Summary","text":"This document tries to describe a simple set of conventions to better enable the adressing of [ActivityPub] objects on servers that support [Client to Server Interactions]. Its main purpose is to formalize a basic vocabulary for defining subsets of IRIs [RFC-3987] for collections in a way that can be generalized to both servers and clients. It builds upon the definition of query parametrs [RFC-3986], by introducing a set of additional operators that can be applied to values.
"},{"location":"fep/6606/#what-we-are-trying-to-solve","title":"What we are trying to solve","text":"The main reason that is often cited as making client to server interactions problematic is that the official specification leaves too much to the implementors' discretion. The fact that there is no proscribed method of filtering collections content is one of the stop gaps that prevents a wider adoption of C2S in services and clients.
"},{"location":"fep/6606/#syntax","title":"Syntax","text":"The following syntax is extracted form RFC-3986 section 3.4, and it formalizes the URL query parameters as representing any string that conforms to the restrictions below between the first \"?\" character and the end of the URL or a \"#\" character marking the start of the fragment part of the URL.
query = *( pchar / \"/\" / \"?\" )\n\npchar = unreserved / pct-encoded / sub-delims / \":\" / \"@\"\n\nunreserved = ALPHA / DIGIT / \"-\" / \".\" / \"_\" / \"~\"\n\npct-encoded = \"%\" HEXDIG HEXDIG\n\nsub-delims = \"!\" / \"$\" / \"&\" / \"'\" / \"(\" / \")\" / \"*\" / \"+\" / \",\" / \";\" / \"=\"\n As we can see this is not formal definition of the query string as a group of key and value pairs, but for our intended use, this is paramount.
So we will take this extra step ourselves and extend the definition to be:
query = *( query-pair [ qconcat ] )\n\nquery-pair = pname [ \"=\" ( pvalue ) ]\n\npname = 1*( pchar )\n\npvalue = *( pchar )\n\npchar = unreserved / pct-encoded / new-delims / \":\" / \"@\"\n\nqconcat = \"&\"\n\nunreserved = ALPHA / DIGIT / \"-\" / \".\" / \"_\" / \"~\"\n\npct-encoded = \"%\" HEXDIG HEXDIG\n\nnew-delims = \"!\" / \"$\" / \"'\" / \"(\" / \")\" / \"*\" / \"+\" / \",\" / \";\"\n To allow for the operators that we want to append to the query values we extend this version with the following elements:
query-pair = p-name [ \"=\" ( [ p-operator ] p-value ) ]\n\np-operator = \"!\" / \"~\"\n\np-value = empty-value / *( pchar )\n\nempty-value = \"-\"\n The operator symbols are \"!\" and \"~\" that correspond to negation respectively fuzzy matching of the query value.
Explicitly, when encountering a URL parameter value that has a \"!\" symbol in front of it we mean it as \"different than\". Similarly when encountering a value prepended with the \"~\" symbol, we mean it as \"similar with\" in a textual manner.
Additionally we added a nil value symbol \"-\" that can be used for operations where the value to compare against is absent.
Please look at the following examples to see how these rules apply and compound with one another:
?element=value\n// resources matching exactly \"value\"\n\n?element=1&element=2\n// resources matching exactly \"1\" or \"2\"\n\n?element=!value1\n// resources inversly matching \"value1\"\n\n?element=!1&element=!2\n// resources inversly matching \"1\" and \"2\"\n\n?element=~fuzzy\n// resources fuzzy matching \"fuzzy\"\n\n?element=~one&element=~two\n// resources fuzzy matching \"one\" or \"two\"\n\n?element=-\n// resources matching empty element values\n\n?element=!-\n// resources matching all non empty element values\n"},{"location":"fep/6606/#applicability-for-activitypub-collections","title":"Applicability for ActivityPub Collections","text":"In this ection we will define how we are going to apply this newly defined schema to use URL query parameters to filter ActivityPub collections.
We will assume a random ActvityPub collection can be found at the folowing URL:
https://example.com/random-collection\n The response to a GET request to this IRI could return something like:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/random-collection\",\n \"name\": \"Random Collection\",\n \"type\": \"OrderedCollection\",\n \"updated\": \"2021-04-09T08:16:05Z\",\n \"first\": \"https://example.com/random-collection?maxItems=10\",\n \"totalItems\": 12,\n \"orderedItems\": [ /* skipping items for brevity */ ]\n}\n The filtering for the elements in the collection will be done by using URL query parameters with the names corresponding to the different properties that we want to filter on for the objects in the collection.
Example:
https://example.com/random-collection?type=Place\n\n// will return only objects with the type `Place` that exist in the\n// Random Collection.\n\nhttps://example.com/random-collection?summary=~test%20example\n\n// will return only objects containing a summary property that contains\n// the \"test example\" text.\n"},{"location":"fep/6606/#implementations","title":"Implementations","text":"The grammar described in this document represents a simplified version of the mechanism that services using the [GoActivityPub] library use for their C2S addressing.
"},{"location":"fep/6606/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/67ff/","title":"FEP-67ff: FEDERATION.md","text":"Authors Status Date received Date final Tracking issue Discussions Repository silverpill @silverpill@mitra.socialFINAL 2023-09-05 2024-09-22 #157 Discussions codeberg"},{"location":"fep/67ff/#summary","title":"Summary","text":"FEDERATION.md is a file containing information necessary for achieving interoperability with a federated service. It was originally proposed by Darius Kazemi on SocialHub forum in Documenting federation behavior in a semi-standard way? topic.
The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/67ff/#structure","title":"Structure","text":"The FEDERATION.md file can have arbitrary structure and content. The only requirements are:
FEDERATION.md file may contain a link to that location.(This section is non-normative.)
# Federation\n\n## Supported federation protocols and standards\n\n- [ActivityPub](https://www.w3.org/TR/activitypub/) (Server-to-Server)\n- [WebFinger](https://webfinger.net/)\n- [Http Signatures](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures)\n- [NodeInfo](https://nodeinfo.diaspora.software/)\n\n## Supported FEPs\n\n- [FEP-67ff: FEDERATION.md](https://codeberg.org/fediverse/fep/src/branch/main/fep/67ff/fep-67ff.md)\n\n## ActivityPub\n\n<!-- Describe activities and extensions. -->\n\n## Additional documentation\n\n<!-- Add links to documentation pages. -->\n"},{"location":"fep/67ff/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/6fcd/","title":"FEP-6fcd: Account Export Container Format","text":"Authors Status Date received Tracking issue Discussions Repository Dmitri Zagidulin @dmitri@social.coopDRAFT 2024-07-11 #357 Discussions codeberg"},{"location":"fep/6fcd/#summary","title":"Summary","text":"This FEP describes a lightweight general purpose account export container format, with the following properties:
Out of scope:
.tar file into a .tar.gz is well known)manifest.yml file that describes what's in the files and directories.tar file.ubc-version: Version of the Account Export Container Format speccontents: A listing of files and directories in this .tar filemeta: A metadata section describing who this export belongs to, what app or service created it, and so on.Example result of exporting an ActivityPub account:
$ tar -vtf ap-account-export-2024-06-11.tar\n\n-rw-rw-r-- 0 0 1K Jun 11 15:38 manifest.yml\ndrwxrwxr-x 0 0 4.0K Jun 11 15:38 activitypub/\ndrwxrwxr-x 0 0 4.0K Jun 11 15:38 key/\n Example corresponding manifest.yml file:
# (Required) Universal Backup Container spec version\nubc-version: 0.1\n\n# (Optional) Metadata section\nmeta:\n created: 2024-01-01\n createdBy:\n # (Optional) URL to a Controller document, such as an ActivityPub profile using FEP-521a\n # @see https://codeberg.org/fediverse/fep/src/branch/main/fep/521a/fep-521a.md\n controller: https://alice-personal-site.example/actor\n # (Optional) The app or service that created this export\n client:\n name: \"Example Exporter App\"\n url: https://codeberg.example.com/example-export-app\n\n# (Required, but can be empty) Contents section, listing the other files and directories\ncontents:\n # This file\n manifest.yml:\n url: https://codeberg.org/fediverse/fep/src/branch/main/fep/6fcd/fep-6fcd.md#manifest-file\n # Directory with ActivityPub-relevant exports\n activitypub:\n contents:\n # Serialized ActivityPub Actor profile\n actor.json:\n url: https://www.w3.org/TR/activitypub/#actor-objects\n # ActivityStreams OrderedCollection representing the contents of the actor's Outbox\n outbox.json:\n url: https://www.w3.org/TR/activitystreams-core/#collections\n following_accounts.csv:\n url: https://docs.joinmastodon.org/user/moving/#export\n followers.csv:\n url: https://docs.joinmastodon.org/user/moving/#export\n lists.csv:\n url: https://docs.joinmastodon.org/user/moving/#export\n bookmarks.csv:\n url: https://docs.joinmastodon.org/user/moving/#export\n blocks.csv:\n url: https://docs.joinmastodon.org/user/moving/#export\n mutes.csv:\n url: https://docs.joinmastodon.org/user/moving/#export\n # Directory of object attachments (post images, etc)\n attachments:\n url: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-attachment\n contents:\n # Actor profile avatar\n avatar.jpg:\n url: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-icon\n # 'key' dir, serialized private/public key pairs,\n # such as those declared in a FEP-521a Actor profile\n key:\n url: https://codeberg.org/fediverse/fep/src/branch/main/fep/521a/fep-521a.md\n contents:\n key-1234.json:\n url: https://www.w3.org/TR/vc-di-eddsa/#representation-eddsa-rdfc-2022\n Example exported key file:
$ cat key/key-1234.json\n {\n \"@context\": [\"https://w3id.org/security/multikey/v1\"],\n \"type\": \"Multikey\",\n \"id\": \"https://alice-personal-site.example/actor#key1234\",\n \"controller\": \"https://alice-personal-site.example/actor\",\n \"publicKeyMultibase\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\",\n \"privateKeyMultibase\": \"z3u2en7t5LR2WtQH5PfFqMqwVHBeXouLzo6haApm8XHqvjxq\"\n}\n"},{"location":"fep/6fcd/#example-blog-archive-format-export","title":"Example Blog Archive Format Export","text":"$ tar -vtf bar-account-export-2024-06-11.tar\n\n-rw-rw-r-- 0 0 1K Jun 11 15:38 manifest.yml\n-rw-rw-r-- 0 0 100K Jun 11 15:38 index.html\n-rw-rw-r-- 0 0 50K Jun 11 15:38 feed.json\ndrwxrwxr-x 0 0 4.0K Jun 11 15:38 uploads/\n Example corresponding manifest.yml file:
ubc-version: 0.1\n\nmeta:\n created: 2024-01-01\n\ncontents:\n # This file\n manifest.yml:\n url: https://codeberg.org/fediverse/fep/src/branch/main/fep/6fcd/fep-6fcd.md#manifest-file\n index.html:\n url: https://indieweb.org/blog_archive_format\n feed.json:\n url: https://indieweb.org/blog_archive_format\n uploads:\n url: https://indieweb.org/blog_archive_format\n"},{"location":"fep/6fcd/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/73cd/","title":"FEP-73cd: Migration User Stories","text":"Authors Status Date received Tracking issue Discussions Repository Bumblefudge <@learningproof.xyz> / @by_caballero@mastodon.socialDRAFT 2024-02-07 #265 - codeberg"},{"location":"fep/73cd/#summary","title":"Summary","text":"In the interest of clarifying and aligning on the problem-space of user account migration, multiple-account management, and export/import/migration of content/activity history, these user stories are offered to organize discussion and solution-sharing.
"},{"location":"fep/73cd/#contributing","title":"Contributing","text":"Any one of these user stories could be further elaborated in the spirit of the SWAT exercise that served as ActivityPub's initial \"definition of done\", perhaps in a separate FEP and/or SocialHub thread.
"},{"location":"fep/73cd/#migration-user-stories","title":"Migration User Stories","text":"PRs welcome! Add or update!
1, 2, and 3 describe follower/profile migration, while 4,5,6 and 7 describe migration of posted activities.
Note: this section is largely subjective and intended as an aid to decision-making, NOT as any kind of definitive statement about the utility or necessity of other FEPs. Feel free to PR in links to blog posts or other tutorials that explain a given implementation of any user stories to the \"profile\" row in the form [1](<link>), [2](<link>)...
push mode) R1 R1 R FEP-7628 \"Move Actor\" (pull mode) R2 R FEP-c390 \"Identity Proofs\" H H R FEP-ae97 \"Client-Side Activity Signing\" R FEP-ef61 \"Portable Objects\" R R H H FEP-8b32 \"Object Integrity Proofs\" (req'd by ^) R^ R^ H^ H^ FEP-521a \"Actor's Public Keys\" H H H H FEP-TBD [Per-user? Per-server? both?] \"Moderation Records\" R H FEP-TBD \"Forwarding and Redirecting Migrated Actors\" H R R? H H R FEP-TBD \"Activity Archives/Export Format\" R R Profiles needed to combine the above?"},{"location":"fep/73cd/#open-questions","title":"Open Questions","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/7458/","title":"FEP-7458: Using the replies collection","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT 2024-04-26 #307 - codeberg"},{"location":"fep/7458/#summary","title":"Summary","text":"Replies are not defined in ActivityPub, as there are no specified side effects for simply encountering objects or activities with inReplyTo set. Most current implementations implicitly reconstruct replies at consumption time, and may silently and implicitly maintain the replies collection at the producer's side. This FEP provides guidance for explicitly using the replies collection, and explicitly managing it.
(This section is non-normative.)
ActivityPub defines side effects for activities, not side effects for encountering objects. One popular use-case in the social web is replying to an object with your own object; this indicates that your object is a response to the other object. However, this is about as far as most implementations go in maintaining the \"reply\" relation. It is generally assumed that objects will be delivered to other servers via a Create activity, and that the object of that Create activity will be cached, and that the \"reply\" will somehow be tracked and attached to the referent object. The ActivityPub spec only mentions \"replies\" in one place, in the context of inbox forwarding as intended to avoid the problem of \"ghost replies\". The implication of this is that senders are expected to address all relevant parties ahead-of-time, as a matter of convention.
What is missing is a mechanism for managing replies that would recognize the canonicity and authority of the actor managing their own replies explicitly, rather than depending on implicit reconstruction after-the-fact. FEP-5624 takes a different approach that doubles down on this implicit reconstruction.
Meanwhile, FEP-7888 specifies a similar mechanism for managing contexts that recognizes the canonicity and authority of the actor managing the conversation explicitly, rather than depending on implicit reconstruction after-the-fact. Implicit reconstruction is usually based on reply trees, but explicit construction is based on Add and Remove activities. The current FEP aims to be written in a way that supports explicit replies management while being orthogonal to the use of context. In other words, the authoring actor has the option to use inReplyTo as metadata, while separately choosing to participate in a context or not.
(This section is non-normative.)
Alice makes a post:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://alice.example/outbox/1\",\n \"summary\": \"Alice created a note\",\n \"actor\": \"https://alice.example\",\n \"type\": \"Create\",\n \"object\": {\n \"id\": \"https://alice.example/objects/1\",\n \"type\": \"Note\",\n \"content\": \"Hello\",\n \"replies\": \"https://alice.example/objects/1/replies\"\n },\n \"cc\": [\"https://alice.example/followers\", \"as:Public\"]\n}\n Bob makes a reply:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://bob.example/outbox/2\",\n \"summary\": \"Bob replied to Alice's note\",\n \"actor\": \"https://bob.example\",\n \"type\": \"Create\",\n \"object\": {\n \"id\": \"https://bob.example/objects/2\",\n \"type\": \"Note\",\n \"content\": \"Hi!\",\n \"inReplyTo\": \"https://alice.example/objects/1\"\n },\n \"to\": \"https://alice.example\",\n \"cc\": [\"https://alice.example/followers\", \"https://bob.example/followers\"]\n}\n Alice explicitly approves Bob's reply, either automatically or manually. Alice forwards Bob's activity to Alice's followers, as requested by Bob. Alice also adds Bob's reply to the replies collection:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://alice.example/outbox/3\",\n \"summary\": \"Alice added Bob's note as a reply\",\n \"actor\": \"https://alice.example\",\n \"type\": \"Add\",\n \"object\": \"https://bob.example/objects/2\",\n \"target\": \"https://alice.example/objects/1/replies\",\n \"to\": \"https://bob.example\",\n \"cc\": [\"https://alice.example/followers\", \"https://bob.example/followers\"]\n}\n Bob sees this and updates the original reply to something offensive:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://bob.example/outbox/4\",\n \"summary\": \"Bob updated a note\",\n \"actor\": \"https://bob.example\",\n \"type\": \"Update\",\n \"object\": {\n \"id\": \"https://bob.example/objects/2\",\n \"type\": \"Note\",\n \"content\": \"Alice sucks lol\",\n \"inReplyTo\": \"https://alice.example/objects/1\"\n },\n \"to\": \"https://alice.example\",\n \"cc\": [\"https://alice.example/followers\", \"https://bob.example/followers\"]\n}\n Perhaps Alice forwards this activity automatically, or perhaps not; at some later point, Alice notices that the reply has been edited. Alice therefore decides to remove the reply:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://alice.example/outbox/5\",\n \"summary\": \"Alice removed a reply\",\n \"actor\": \"https://alice.example\",\n \"type\": \"Remove\",\n \"object\": \"https://bob.example/objects/2\",\n \"target\": \"https://alice.example/objects/1/replies\",\n \"to\": \"https://bob.example\",\n \"cc\": [\"https://alice.example/followers\", \"https://bob.example/followers\"]\n}\n Bob maliciously chooses not to forward this removal activity.
Charlie follows Bob, but not Alice. Charlie's client has received activities 2, 3, and 4 in Charlie's inbox.
Under current behavior, Charlie would have fetched Alice's post, but not have checked Alice's replies collection or used it in any way. Bob's reply would still be visible as a reply to Alice's post.
Under proposed behavior, Charlie would instead defer to checking Alice's replies collection. Bob's reply would not be contained in that collection, as Alice has removed it. Therefore, Charlie does not see or treat Bob's reply as a verified reply.
inReplyTo set, ActivityPub clients SHOULD verify that the current object is indeed a valid member of the inReplyTo.replies collection. (A further FEP may provide a mechanism to establish proof of this without having to check the entire collection.)inReplyTo is set to some object attributedTo the current actor, an ActivityPub actor SHOULD send an activity with the following properties:type array MUST include Addobject MUST be the object declaring an inReplyTotarget MUST be your own object's replies collectionobject.attributedTo actor, as well as potentially their followers, in addition to any other addressed actors (such as your own followers or some other audience)replies collection that they own by sending an activity with the following properties:type array MUST include Removeobject MUST be the object declaring an inReplyTotarget MUST be your own object's replies collectionobject.attributedTo actor, as well as potentially their followers, in addition to any other addressed actors (such as your own followers or some other audience)CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/7502/","title":"FEP-7502: Limiting visibility to authenticated actors","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT 2023-12-24 #218 - codeberg"},{"location":"fep/7502/#summary","title":"Summary","text":"Some servers require authentication for all requests made via ActivityPub, even for GET requests on public objects addressed to as:Public. This violates the requirement that anything addressed to as:Public is made available without requiring authentication. This FEP proposes an alternative addressing that may be used in such scenarios, signaling that the object is not fully public but is otherwise available to any actor.
(This section is non-normative.)
ActivityPub issue 339 discusses the shortcomings of as:Public and proposed a new delivery/addressing target of as:Authenticated, but this proposal was never adopted. The distinction between the two is as follows:
Public : Available to anyone without authentication
Authenticated : Available to any actor after authentication
Issues raised with this approach include:
The concern with semantic clarity is unaddressed, as replacing as:Public with a different mechanism to signal fully public objects is out-of-scope for this FEP. However, this mechanism is still useful for addressing different concerns:
In WAC, there is a distinction made between an \"agent\" (http://xmlns.com/foaf/0.1/Agent) and an \"authenticated agent\" (http://www.w3.org/ns/auth/acl#AuthenticatedAgent). This FEP proposes an addressing target for the latter.
In addition to collections and objects, Activities may additionally be addressed to a special \"authenticated\" collection, with the identifier http://www.w3.org/ns/auth/acl#AuthenticatedAgent.
Activities addressed to this special URI shall be accessible to all actors, with authentication. Implementations MUST NOT deliver to the \"authenticated\" special collection; it is not capable of receiving actual activities.
An activity SHOULD NOT be addressed to both \"public\" and \"authenticated\". In such a case, addressing to \"authenticated\" has no effect while also addressed to \"public\".
"},{"location":"fep/7502/#overview","title":"Overview","text":"to or cc or includes in the audience the \"authenticated agents\" special identifier.Pixelfed intends to implement this FEP when \"signed fetch\" is enabled, such that the resulting data returned after a signed fetch does not include as:Public.
http://www.w3.org/ns/auth/acl#AuthenticatedAgent Label Anyone authenticated Subclass of Agent Is defined by Basic Access Control ontology"},{"location":"fep/7502/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/7628/","title":"FEP-7628: Move actor","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT informational 2023-09-20 #168 Discussions codeberg"},{"location":"fep/7628/#summary","title":"Summary","text":"Migration of followers from one ActivityPub actor to another.
"},{"location":"fep/7628/#history","title":"History","text":"Move activity is defined in Activity Vocabulary.
Mastodon started using Move activity for migrating accounts in 2019. The activity is sent by the old server to actor's followers and actors who receive this activity un-follow the old account and follow the new account.
The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/7628/#linked-actors","title":"Linked actors","text":"One persona can be represented by one or more actors. Different actors are considered associated with one persona if and only if they are linked. Possible ways to link actors:
alsoKnownAs property of an actor object. This method is the most popular, it is used by Mastodon and many other ActivityPub servers.subject property.Among actors associated with a persona, there MUST be one primary actor. Migration of an account means the change of a primary actor.
"},{"location":"fep/7628/#move-activity","title":"Move activity","text":"Move activity is used to notify other actors about the migration. It MUST have the following properties:
type: the type of activity MUST be Move.actor: the actor performing the migration.object: the old primary actor ID.target: the new primary actor ID.The activity is considered valid if object and target actors are linked.
Upon receiving valid Move activity, all actors following object MAY un-follow it by sending Undo(Follow) activity and MUST either send Follow activity to the target or otherwise notify the user that their contact has moved.
This activity comes in two sub-types:
Move sent by the old actor (push mode). In this case, the old server needs to be online.Move sent by the new actor (pull mode). In this case, the old server doesn't need to be online, but the person using the actor needs to have a reserve copy of the follower list.Example (activity subtype 1, push mode):
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://server1.example/activities/9b496346-fa69-40ac-bc4e-7ac06192abe1\",\n \"type\": \"Move\",\n \"actor\": \"https://server1.example/users/alice\",\n \"object\": \"https://server1.example/users/alice\",\n \"target\": \"https://server2.example/users/alice\",\n \"to\": \"https://www.w3.org/ns/activitystreams#Public\",\n \"cc\": \"https://server1.example/users/alice/followers\"\n}\n Example (activity subtype 2, pull mode):
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://server2.example/activities/9b496346-fa69-40ac-bc4e-7ac06192abe1\",\n \"type\": \"Move\",\n \"actor\": \"https://server2.example/users/alice\",\n \"object\": \"https://server1.example/users/alice\",\n \"target\": \"https://server2.example/users/alice\",\n \"to\": \"https://www.w3.org/ns/activitystreams#Public\",\n \"cc\": \"https://server2.example/users/alice/followers\"\n}\n"},{"location":"fep/7628/#movedto-and-copiedto-properties","title":"movedTo and copiedTo properties","text":"If previous primary actor was deactivated after migration, it MUST have movedTo property containing the ID of the new primary actor. Publishers SHOULD NOT deliver activities to actor's inbox if movedTo property is present.
If previous primary actor is not deactivated, copiedTo property MUST be used.
After adding the movedTo or copiedTo property, the actor SHOULD notify its followers about the change by publishing an Update activity.
The location of the new primary actor. The actor containing this property should be considered inactive.
https://w3id.org/fep/7628#movedToThe location of the new primary actor.
https://w3id.org/fep/7628#copiedToCC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/76ea/","title":"FEP-76ea: Conversation Threads","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.orgDRAFT 2024-10-04 #407 Discussions codeberg"},{"location":"fep/76ea/#summary","title":"Summary","text":"This FEP defines a way to identify the conversation thread of an object with Activity Streams 2.0.
"},{"location":"fep/76ea/#motivation","title":"Motivation","text":"Threaded conversations are a common data structure for social software. This is defined as a tree with the original post at its root, replies to that post as child nodes, all replies to those replies as their children, and so on recursively.
Some social software restricts the depth of the thread, while others allow for unlimited depth.
Identifying the thread that an AS2 object is part of allows for the construction of a conversation view of the thread.
It is possible with Activity Streams 2.0 to construct a conversation thread by following the inReplyTo property of an object until the original post is found, and then expanding the replies property of the original post recursively. With ActivityPub, however, this can require a lot of different HTTPS requests to different servers, which can be slow and inefficient.
This FEP defines an extension property, thread, that can be used to identify the conversation thread of an object.
ActivityPub is the primary use case for Activity Streams 2.0, but not the only one. Where specific processing requirements of ActivityPub implementations are made, they are specifically noted. General processing hints for other use cases are also provided.
"},{"location":"fep/76ea/#user-stories","title":"User stories","text":"These are some user stories for threading in conversations.
inReplyTo chain and following the replies collections, but it can be slow and inefficient. Comparing a thread identifier found in each object can be much faster.inReplyTo chain and replies collections can be slow and inefficient, especially if the thread is deep or has many objects.inReplyTo and replies, and possibly requires fetching every single replies collection in the tree.replies collection is not sufficient, because it only contains direct replies to the object, not the full conversation tree.The context URL for this FEP is https://purl.archive.org/socialweb/thread.
The context is as follows:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"thr\": \"https://purl.archive.org/socialweb/thread#\",\n \"thread\": {\n \"@id\": \"thr:thread\",\n \"@type\": \"@id\"\n },\n \"root\": {\n \"@id\": \"thr:root\",\n \"@type\": \"@id\"\n }\n}\n"},{"location":"fep/76ea/#terms","title":"Terms","text":"The context defines two properties.
"},{"location":"fep/76ea/#thread","title":"thread","text":"The thread property is an OrderedCollection that contains all of the objects in the conversation thread. The collection is ordered in reverse chronological order, with the most recent object first.
The thread collection does not directly represent the tree structure of the conversation thread; it is a flat list of objects. The tree structure can be reconstructed by following the inReplyTo and/or replies properties of each object in the collection.
The thread property extends the context property from the Activity Vocabulary.
The thread property does not replace the replies property of an object. replies contains the possibly curated collection of direct replies to the object; thread contains the full conversation tree, up- and down-thread.
root","text":"The root property is an Object that is the original post of the conversation thread. The root property is usually the last (earliest) object in the thread collection.
This property gives an easy way for a consumer to find the root post of the thread without having to search the orderedItems collection, navigate through multiple OrderedCollectionPage pages, or traverse the inReplyTo properties of the objects in the collection.
Note that thread and root are partially inverse properties. The thread property of the root property of a collection SHOULD contain the id of the thread collection. However, the root property of the thread property of an object MAY not contain the object's id, because the object is in the thread, but is not the root.
This covers recommended behavior for processors that implement the thread property.
When a publisher creates a new content object that is not a reply to any others, it should include a new, unique collection as its thread property. The collection should contain only the new object. The thread collection should be addressed to all the same addressees as the original object.
When a publisher is creating a new content object with an inReplyTo property, the publisher SHOULD use the thread property of the object being replied to as the thread property of the new object. The addressees of the new object should include the creator of the original post, identified by the attributedTo property of the original post or the attributedTo property of the thread collection.
Replies can be created to multiple other objects; the inReplyTo property can be an array. The thread property can also be an array, with more or fewer values than the inReplyTo. Each thread property should correspond to the thread property of an object in the inReplyTo array.
To branch a content object into its own conversation thread, the publisher should create an Announce activity that includes the new object as the object property. The Announce activity should have a new, unique thread property. The Announce activity can include a content property.
To graft a content object into a different thread than the ones it is already part of, the publisher should create an Announce activity that includes the new object as the object property. The Announce activity should have the thread property of the new thread, and an inReplyTo property that matches one of the objects in the thread. The Announce activity can include a content property.
As with the replies property, the processor implementing the original post of a thread SHOULD maintain the thread collection by adding new objects to the collection as they are received.
In ActivityPub, this could be done when the processor receives an object with an inReplyTo property that matches an object in the thread collection.
To facilitate collection synchronization, the processor SHOULD distribute an Add activity to the audience of the original object with the new object as the object property and the thread as the target property.
However, private replies \"down-thread\" may not be addressed to the author of the original post and may not be available to the processor for the original post.
The processor implementing the original post MAY curate the thread collection by filtering objects from the collection. This could be done to remove spam, off-topic, or abusive content from the thread.
In ActivityPub, if an object is removed from the thread, he processor SHOULD distribute a Remove activity to the audience of the original object with the new object as the object property and the thread as the target property.
The tree structure of the thread should be maintained; every object in the thread collection, except the root, should have an inReplyTo property that matches the id of another object in the collection. If the processor removes an object from the collection, it SHOULD remove all objects that are in reply to that object, and their replies, and so on.
The replies property of objects in the thread collection MAY be maintained by other processors. Curation of the replies collections or of the thread collection may mean that objects may be omitted from one collection or the other. However, the replies collection of the original post SHOULD be a subset of the thread collection.
An example of a Note object with a thread property:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/note/123\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/user/1\",\n \"to\": [\n \"https://remote.example/user/17\",\n \"https://remote.example/user/17/followers\"\n ],\n \"content\": \"I concur!\",\n \"thread\": \"https://remote.example/thread/117\",\n \"inReplyTo\": \"https://remote.example/note/117\"\n}\n"},{"location":"fep/76ea/#example-2","title":"Example 2","text":"An example of an Image object with a thread property. The Image is a root or original post with no inReplyTo property:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/image/123\",\n \"type\": \"Image\",\n \"name\": \"A photo of a cat\",\n \"attributedTo\": \"https://example.com/user/1\",\n \"to\": \"https://example.com/user/1/followers\",\n \"url\": {\n \"type\": \"Link\",\n \"mediaType\": \"image/jpeg\",\n \"href\": \"https://example.com/image/123.jpg\"\n },\n \"replies\": \"https://example.com/replies/123\",\n \"thread\": {\n \"id\": \"https://example.com/thread/123\",\n \"to\": \"https://example.com/user/1/followers\",\n \"type\": \"OrderedCollection\",\n \"totalItems\": 4,\n \"orderedItems\": [\n {\n \"id\": \"https://fourth.example/note/721\",\n \"attributedTo\": \"https://fourth.example/user/4\",\n \"to\": [\n \"https://example.com/user/1\",\n \"https://example.com/user/1/followers\",\n \"https://other.example/user/2\"\n ],\n \"inReplyTo\": \"https://other.example/note/338\"\n },\n {\n \"id\": \"https://third.example/note/992\",\n \"attributedTo\": \"https://third.example/user/3\",\n \"to\": \"https://example.com/user/1\",\n \"inReplyTo\": \"https://example.com/image/123\"\n },\n {\n \"id\": \"https://other.example/note/338\",\n \"attributedTo\": \"https://other.example/user/2\",\n \"to\": [\n \"https://example.com/user/1\",\n \"https://example.com/user/1/followers\"\n ],\n \"inReplyTo\": \"https://example.com/image/123\"\n },\n \"https://example.com/image/123\"\n ]\n }\n}\n Note that not all objects in the thread collection need to be addressed to the same audience. The audience of the thread collection is the audience of the original post.
This is a Note object that is a reply to two different objects, and thus is part of two different threads.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/note/789\",\n \"attributedTo\": \"https://example.com/user/1\",\n \"to\": \"as:Public\",\n \"content\": \"These are both good points.\",\n \"inReplyTo\": [\n \"https://remote.example/note/57\",\n \"https://other.example/note/456\"\n ],\n \"thread\": [\n \"https://remote.example/thread/57\",\n \"https://other.example/thread/456\"\n ]\n}\n"},{"location":"fep/76ea/#example-4","title":"Example 4","text":"Objects in a thread that have been deleted by their author can be represented in the thread collection with a Tombstone object.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/note/345\",\n \"attributedTo\": \"https://example.com/user/1\",\n \"to\": \"as:Public\",\n \"content\": \"Activity Streams 2.0 is awesome!\",\n \"replies\": \"https://example.com/replies/345\",\n \"thread\": {\n \"id\": \"https://example.com/thread/345\",\n \"to\": \"as:Public\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n {\n \"id\": \"https://third.example/note/567\",\n },\n {\n \"type\": \"Tombstone\",\n \"id\": \"https://remote.example/note/456\",\n \"inReplyTo\": \"https://example.com/note/345\",\n \"deleted\": \"2024-10-03T00:00:00Z\"\n },\n \"https://example.com/note/345\"\n ]\n }\n}\n"},{"location":"fep/76ea/#example-5","title":"Example 5","text":"The thread collection can be paged, as with other collections.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/note/678\",\n \"attributedTo\": \"https://example.com/user/1\",\n \"to\": \"as:Public\",\n \"content\": \"Is Wario A Libertarian?\",\n \"replies\": \"https://example.com/replies/678\",\n \"thread\": {\n \"id\": \"https://example.com/thread/678\",\n \"to\": \"as:Public\",\n \"type\": \"OrderedCollection\",\n \"totalItems\": 244780,\n \"first\": \"https://example.com/thread/678/page/12239\",\n \"last\": \"https://example.com/thread/678/page/1\"\n }\n}\n"},{"location":"fep/76ea/#example-6","title":"Example 6","text":"The root property can be used to identify the original post of a thread.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/thread/654\",\n \"type\": \"OrderedCollection\",\n \"totalItems\": 457,\n \"first\": \"https://example.com/thread/654/page/23\",\n \"last\": \"https://example.com/thread/654/page/1\",\n \"root\": \"https://example.com/note/654\"\n}\n"},{"location":"fep/76ea/#example-7","title":"Example 7","text":"To branch an object to a new conversation, an Announce activity is used.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/announce/123\",\n \"to\": \"as:Public\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/user/1\",\n \"thread\": \"https://example.com/thread/123\",\n \"content\": \"I think this note is important and I want to start a separate discussion about it.\",\n \"object\": {\n \"id\": \"https://example.com/note/456\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.org/user/2\",\n \"thread\": \"https://example.net/thread/789\",\n \"inReplyTo\": \"https://example.net/note/foo\",\n \"to\": \"as:Public\",\n \"content\": \"Trains are great.\"\n }\n}\n"},{"location":"fep/76ea/#example-8","title":"Example 8","text":"To graft an object to an existing conversation, an Announce activity is used.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/announce/456\",\n \"to\": \"as:Public\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/user/1\",\n \"thread\": \"https://social.example/thread/222\",\n \"inReplyTo\": \"https://social.example/note/888\",\n \"content\": \"This comment about trains from another thread seems relevant here.\",\n \"object\": {\n \"id\": \"https://example.com/note/456\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.org/user/2\",\n \"thread\": \"https://example.net/thread/789\",\n \"inReplyTo\": \"https://example.net/note/foo\",\n \"to\": \"as:Public\",\n \"content\": \"Trains are great.\"\n }\n}\n"},{"location":"fep/76ea/#security-considerations","title":"Security Considerations","text":"Not all objects in the thread collection may be addressed to the same audience. Representations of the collection SHOULD NOT include the content property or other sensitive information from objects in the collection that are not addressed to the recipient of the representation.
In ActivityPub, the orderedItems property of the thread collection MAY be filtered for the recipient of the representation.
The ostatus:conversation property is used in Mastodon and elsewhere to identify the thread of an object, but it is not necessarily dereferenceable.
Some implementations of ActivityPub use the context property to represent the thread of an object. This FEP provides a more specific property, which frees up the \"intentionally vague\" context property for other uses. It also avoids the confusing clash with the @context property of JSON-LD.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/7888/","title":"Index","text":"hide: - navigation description: \"ActivityStreams Vocabulary defines the context property, but it is \"intentionally vague\". Unfortunately, this makes the definition so vague as to be practically useless. This FEP aims to provide more guidance on possible uses of the context property, as well as formalizing some best practices.\"
"},{"location":"fep/7888/#fep-7888-demystifying-the-context-property","title":"FEP-7888: Demystifying the context property","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT 2023-03-14 #68 Discussions codeberg"},{"location":"fep/7888/#summary","title":"Summary","text":"ActivityStreams Vocabulary defines the context property, but it is \"intentionally vague\". Unfortunately, this makes the definition so vague as to be practically useless. This FEP aims to provide more guidance on possible uses of the context property, as well as formalizing some best practices.
(This section is non-normative.)
See \"Appendix A: Rationale\" for fuller analysis of the definition, as well as use cases that can be mapped onto context.
In short:
context, audience and tag can be used for similar purposes.context roughly corresponds to the label \"was created in relation to\". Compare to audience, which roughly corresponds to the label \"is considered relevant to\". Compare also to tag, which roughly corresponds to the label \"is associated with a topic of\".context embodies purpose. Things grouped by the same context \"belong together\" in a way that can't be said about things grouped by the same tag.context exist within that context, and are meant to be seen and interpreted in context of something else. In most cases, you do not want to view the object on its own; it should be viewed together with other objects, contextually. Deleting a context might reasonably delete or garbage-collect objects within that context.context to represent a \"thread\", \"topic\", \"conversation\", \"room\", \"channel\", \"forum\", \"wall\", \"guild\", \"space\", \"project\", or so on.The requirements below can be summarized like so:
context for signaling which objects belong together purposefully, i.e. objects that are meant to be viewed or processed together. Ideally, make the context resolve to something useful. Depending on which properties the resolved context has, various use cases can make use of those properties.attributedTo some owner, others can keep that owner in the loop when interacting with their context. Similar considerations apply to followers and audience.context to group related objects by the context's id. The graph source for those objects is up to you, but in the case where your source is an SQL database, it probably makes sense to have the context.id be the value of a column which is indexed so you can efficiently use a WHERE clause in your query.context might exist within that same context, or they might declare their own context, or they might not declare a context. If you're declaring someone else's context, then you might want to keep them in the loop, in the same way that you'd keep someone in the loop if you replied to their object or tagged their object. This would be signaled via context.attributedTo similarly to inReplyTo.attributedTo or tag[*].attributedTo.context","text":"When generating an object with a context as a publisher:
A context SHOULD have a purpose; consider tag for looser references. Objects sharing a certain context SHOULD be strongly related and intended to be viewed in the same grouping. Deleting the context MAY delete all objects within that context.
A context SHOULD be resolvable. The resolved object or link can describe the context with at least the additional information needed to fully process the activity or object. Examples of generally useful properties include but are not limited to:
attributedTo denotes the authority for that context. (This authority might be a good target for addressing and delivery of related activities.)audience indicates intent for or potential interest by some entities. (These entities might be a good target for addressing and delivery of related activities.)followers signals that the context might be a followable object. (This followers collection might be a good target for addressing and delivery of related activities.)outbox might contain relevant Activities performed by the context, if it is an actor.Ideally, the resolved context SHOULD in some way have an associated Collection which can contain the related items. (The exact semantics of discovering this Collection are out of scope of this FEP.)
context","text":"When encountering an object with a context as a consumer or browser:
context","text":"At minimum, you SHOULD consider the current object alongside other objects referencing the same context (by id) instead of considering the current object independently. By default, the graph source for objects that are being considered for inclusion is arbitrary. This can be some dataset, or it can be some relevant collection's items. For example, you might conssider the outbox and/or inbox of one or more actors, or you might consider a specific property path on the context (if resolvable).
If the context resolves to an object of a certain type, then that type MAY indicate that a certain relation represents a canonical Collection of all objects that the authority considers to be included. (The definition of such types and relations is out of scope of this FEP.)
For authoritative contexts that include such a canonical Collection, you SHOULD NOT assume that an object has been accepted into that collection simply because it declares context. Consumers SHOULD make efforts to verify reverse claims of inclusion. If a client or user-agent is unable to verify this claim, then the client or user-agent SHOULD indicate to users that the object's claim of being included in the authoritative context is unverified. Criteria for establishing proof of inclusion in a collection is out of scope for this FEP, but might include:
context","text":""},{"location":"fep/7888/#choosing-whether-to-participate-in-the-same-context","title":"Choosing whether to participate in the same context","text":"When encountering an object with a context and choosing to author your own object or activity that interacts with this object:
context as-is, if you wish for your object to be included in that same context.context, if you wish for your object to exist in a different context.context entirely, if you wish for your object to exist on its own.Note that context can be present on either the object, the activity, or both. It is also possible for different context references to be placed on each. This depends on how context is used within a given protocol. Protocol considerations for when to use certain contexts are out-of-scope for this FEP. Protocol considerations for how to negotiate participation in someone else's context are also out-of-scope for this FEP.
Per PUB Section 6.1 \"Client Addressing\":
Clients SHOULD look at any objects attached to the new Activity via the object, target, inReplyTo and/or tag fields, retrieve their actor or attributedTo properties, and MAY also retrieve their addressing properties, and add these to the to or cc fields of the new Activity being created. Clients MAY recurse through attached objects, but if doing so, SHOULD set a limit for this recursion. (Note that this does not suggest that the client should \"unpack\" collections of actors being addressed as individual recipients).
Clients MAY give the user the chance to amend this addressing in the UI.
This FEP extends the recommendation to look at object, target, inReplyTo, and/or tag to also include context.
If copying someone else's context, you SHOULD send your activity to the owner(s) of the context(s), defined via context.attributedTo if resolvable. This is similar to how one might address the author of an object that they are responding to via inReplyTo.attributedTo, as a social courtesy. You MAY also want to address context.followers and/or addressing properties like context.audience.
(This section is non-normative.)
"},{"location":"fep/7888/#the-existing-definition","title":"The existing definition","text":"From the current definition in VOCAB: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-context
Identifies the context within which the object exists or an activity was performed.
The notion of \"context\" used is intentionally vague. The intended function is to serve as a means of grouping objects and activities that share a common originating context or purpose. An example could be all activities relating to a common project or event.
Aside from being \"intentionally vague\", the definition is also somewhat circular; it requires knowing what a context is and having some conceptual understanding of the notion of \"context\". However, we are given some guidance towards its \"intended function\", which is to group objects by some common purpose or origin.
"},{"location":"fep/7888/#supporting-statements-from-spec-authors","title":"Supporting statements from spec authors","text":"In a GitHub issue from ActivityStreams 2.0's development cycle, James Snell provides the following example:
{\n \"type\": \"Note\",\n \"content\": \"This is a note\",\n \"scope\": {\n \"type\": \"Organization\",\n \"name\": \"My Employer\"\n },\n \"to\": [\"john@example.com\", \"sally@example.com\"],\n \"context\": {\n \"type\": \"http://example.org/types/Project\",\n \"name\": \"A Project\"\n }\n}\n James Snell then comments that:
scope indicates that the audience for the note is only members of the Organization.to indicates specific people who should be actively notified.context indicates a larger context within which the note exists.James Snell then clarifies (emphasis added):
scope is not access control [...] a consuming implementation may include the note on the activity timeline of anyone associated with the \"My Employer\" organization, but it would only notify two individuals listed by the to property. The context property, on the other hand, has absolutely nothing to do with audience targeting. The above note is essentially saying, \"This is a note that was created in relation to A Project. Make the note available to anyone in the My Employer organization but specifically notify John and Sally\"
Therefore, we can establish that context as a property roughly translates to a label of \"was created in relation to\".
In a separate issue, James Snell provides another explanation:
The context is really intended to allow objects and activities to be logically grouped. For instance, in an enterprise setting, the context may group activities by project while the scope would identify one or more teams for which the activity is considered relevant, while the to/cc fields are used to indicate specific individuals to notify.
scope was later renamed to audience, but the two properties remain closely related and are presented together in AS2-VOCAB Section 5.1.1 \"Audience and Context\":
Activities are rarely isolated events. Often, multiple individual activities will be performed around a similar context or audience. For instance, a collaborators working on a shared project might perform multiple related activities in the process of achieving some goal. Such activities can be logically grouped together using the context property, and scoped to a particular audience using the audience property.
"},{"location":"fep/7888/#purpose-and-intent-or-why-not-use-a-tag","title":"Purpose and intent; or, why not use a tag?","text":"We might similarly use a tag for grouping objects and activities. Several fediverse projects often include a Hashtag (defined as an extension within the ActivityStreams namespace, but not actually adopted or defined formally). This Hashtag signals an intent to be included or discovered through a collection of objects bearing the same Hashtag, uniquely identified by its name. The maintenance of such implicit collections is assumed to be the responsibility of the receiving server, although an href might be provided for convenience, in order to browse the implicit collection of tagged objects as seen from that origin server. (This also makes the Hashtag a sub-type of Link.)
The key property of such a tag is to signal a general, implicit association by reference. We might then consider a context to be an explicit association, but such an explicit association requires an explicit definition.
"},{"location":"fep/7888/#the-different-types-of-context-and-how-they-are-actually-the-same","title":"The different types of context, and how they are actually the same","text":"Various dictionaries define context generally as something that helps you understand the situation. Following from this, the context should be something that helps you process the activity or object. Ignoring the context may lead to misunderstanding the activity or object; the object or activity exists within that context, and should be understood in context of that context.
Specific contexts can be thought of in several applications:
We might continue to articulate further types of contexts, but the general pattern that emerges is that a context exists to form a purposeful grouping, regardless of the specific purpose. For example, if we had the notion of a conversation, then we might reasonably say that someone owns this conversation and can apply their authority to it. Looking at some object or activity within this context is generally not recommended on its own; it is better to view the entire conversation or some page of it rather than viewing a singular object.
"},{"location":"fep/7888/#sample-workflows-and-use-cases-involving-context","title":"Sample workflows and use-cases involving context","text":"The context may be presented using the following abstractions:
Contexts may be associated with other contexts:
It is also possible to not have a context. Such objects exist only in the general context of their author (via attributedTo) or other implicit contexts, and are otherwise self-sufficient.
Considerations on when to use context include:
(This section is non-normative.)
"},{"location":"fep/7888/#example-1-a-minimal-example-for-grouping-objects-by-context","title":"Example 1: A minimal example for grouping objects by context","text":"This example demonstrates how objects sharing the same context can be logically grouped together.
You encounter the following object:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-object\",\n \"context\": \"https://domain.example/some-context\",\n \"summary\": \"<some-object> exists in <some-context>.\"\n}\n You wish to participate in the same context, so you dereference the context in order to learn more about it:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-context\",\n \"attributedTo\": \"https://domain.example/context-owner\",\n \"summary\": \"<some-context> is owned by <context-owner>.\"\n}\n You create an object, while copying that context onto your object:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/your-object\",\n \"context\": \"https://domain.example/some-context\",\n \"summary\": \"<your-object> exists in <some-context> as well.\"\n}\n Distribution occurs somehow; you may want to notify the <context-owner> or seek their acknowledgement of your object, but these things are out-of-scope of this example. A graph source or dataset containing these two objects may be queried for objects sharing the same context:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/results-for-your-query\",\n \"type\": \"Collection\",\n \"summary\": \"The <results-for-your-query> show that 2 items have a context of <some-context>. They are <some-object> and <your-object>.\",\n \"totalItems\": 2,\n \"items\": [\n \"https://domain.example/some-object\",\n \"https://domain.example/your-object\"\n ]\n}\n"},{"location":"fep/7888/#example-2-choosing-not-to-participate-in-the-same-context","title":"Example 2: Choosing not to participate in the same context","text":"This example demonstrates how objects can have different contexts, indicating that they were created for different purposes. Though they may be grouped by other criteria, they do not share a primary reason for existing.
You encounter the following object:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-object\",\n \"context\": \"https://domain.example/some-context\",\n \"summary\": \"<some-object> exists in <some-context>.\"\n}\n You want to establish your own context, separately from the current object's context:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/a-different-context\",\n \"attributedTo\": \"https://domain.example/you\",\n \"summary\": \"<a-different-context> is owned by <you>.\"\n}\n You may declare that your object is in some way a response to the object that you encountered, but because the contexts are the same, they do not share a primary grouping:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/your-object\",\n \"inReplyTo\": {\n \"id\": \"https://domain.example/some-object\",\n \"context\": \"https://domain.example/some-context\",\n \"summary\": \"<some-object> exists in <some-context>.\"\n },\n \"context\": \"https://domain.example/a-different-context\",\n \"summary\": \"<your-object> is a response to <some-object>, but <some-object> exists in <some-context> while <your-object> exists in <a-different-context>.\"\n}\n Querying replies for the original object might surface your object, but querying the context for the original object will not surface your object.
Later, <some-context> is deleted. In some cases, <some-object> might be garbage-collected, since it has lost its reason or purpose for existing; at best, it is considered orphaned. However, <your-object> continues to exist because it was created in <a-different-context> which still exists.
This example demonstrates how one might deal with objects that have multiple contexts.
You encounter an object with multiple contexts:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-object\",\n \"context\": [\"https://domain.example/some-context\", \"https://domain.example/some-other-context\"],\n \"summary\": \"<some-object> exists in <some-context> and <some-other-context>.\"\n}\n You dereference the two contexts:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-context\",\n \"attributedTo\": \"https://domain.example/context-owner\",\n \"summary\": \"<some-context> is owned by <context-owner>.\"\n}\n {\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-other-context\",\n \"attributedTo\": \"https://domain.example/other-context-owner\",\n \"type\": \"Object\",\n \"summary\": \"<some-other-context> is owned by <other-context-owner>.\"\n}\n As a third-party observer, you can choose to browse either context.
As a third-party interactor, you can choose to declare an object in either context, both contexts, a different context, or no context.
The protocol considerations for which contexts are considered valid or acceptable are out-of-scope of this FEP, but dereferencing the contexts can provide more information that can help you make this choice. Perhaps you expect a certain type to be declared, or perhaps you require an owner, or perhaps some other criteria is enforced.
"},{"location":"fep/7888/#example-4-publishing-consuming-and-interacting-with-authoritative-contexts-that-have-canonical-collections","title":"Example 4: Publishing, consuming, and interacting with authoritative contexts that have canonical collections","text":"This example demonstrates how one might expose all objects acknowledged by a context owner to exist within the context.
You encounter the following object:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-object\",\n \"context\": \"https://domain.example/some-context\",\n \"summary\": \"<some-object> exists in <some-context>.\"\n}\n You wish to browse that context, so you dereference the context:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-context\",\n \"attributedTo\": \"https://domain.example/context-owner\",\n \"type\": \"https://w3id.org/fep/xxxx/Conversation\",\n \"https://w3id.org/fep/xxxx/posts\": {\n \"id\": \"https://domain.example/some-context/posts\",\n \"type\": \"OrderedCollection\",\n \"items\": [\n \"https://domain.example/some-object\",\n // ...\n ]\n }\n \"summary\": \"<some-context> is owned by <context-owner>. It is a <Conversation> and it has a canonical collection of <posts>, which is <some-context/posts>.\"\n}\n As a consumer, you can browse or backfill the conversation by loading the context's canonical collection. In the above representation of <some-context>, the use of the hypothetical https://w3id.org/fep/xxxx/Conversation type would indicate that the associated canonical collection is exposed via the hypothetical https://w3id.org/fep/xxxx/posts property.
(This section is non-normative.)
Because PUB does not define the use of context as a property or the notion of a canonical collection, it is up to ActivityPub Clients to manage contexts and their canonical collections for themselves. The following algorithm may be used to create an object within a context that has a canonical collection:
Create the canonical Collection that will be associated with the context. Save the generated Collection id to be used in the next step.context. If the Object has a canonical Collection associated with it, then specify the appropriate property relation using the id from the previous step. Save the generated context id to be used in the next step.context as the id from the previous step. Set an appropriate audience or use to/cc to deliver the Create activity as-is. Save the generated object id to be used in the next step.ids obtained from the responses for steps 1 and 3. You may wish to deliver this Add activity via to/cc/audience targeting your intended recipients, especially if you did not deliver the Create Object from step 3.CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/7952/","title":"FEP-7952: Roadmap For Actor and Object Portability","text":"Authors Status Date received Tracking issue Discussions Repository Dmitri Zagidulin @dmitri@social.coop, bumblefudge bumblefudge@learningproof.xyzDRAFT 2024-06-20 #435 Discussions codeberg"},{"location":"fep/7952/#summary","title":"Summary","text":"Portability: A set of design choices, data models, and protocols, that enable an end-user to automatically migrate from one service provider to another with the least amount of data loss and service disruption, including loss of or disruption to the user's social graph (Followers and Following collections, etc).
We focus on automated migration because the option of fully manual migration always exists, such as re-typing all of one's messages and content, manually re-adding everyone to one's following collection, manually contacting everyone on one's former followers list and asking them to re-follow, and so on. However, that's an extreme failure state in usability terms; we want to avoid that.
This FEP targets two main categories of migration, covering user stories 1A-1F and 2 + 3 in FEP-73cd: User Migration Stories, respectively. It also draws inspiration from an earlier information document, FEP-cd47: Federation-friendly Addressing and Deduplication Use-Cases.
"},{"location":"fep/7952/#migration-from-a-live-cooperating-server","title":"Migration from a live (cooperating) server","text":"Note that the data handling and formatting recommendations made here will be strictly complementary to the in-progress LOLA proposal, which target the same user stories.
"},{"location":"fep/7952/#migration-from-a-backupexport-produced-by-a-dead-or-non-cooperating-server","title":"Migration from a backup/export produced by a dead (or non-cooperating) server","text":""},{"location":"fep/7952/#current-approaches","title":"Current Approaches","text":"Most current Fediverse implementations tend to follow a pattern of comprehensive service providers (or instances) that bundle the following concerns into a single service running on a web domain:
This tight coupling of concerns constrains the possibilities for migrating from a live server, and the daunting task of automating migrations to other, less-bundled architectures has yet to be attempted. Its strict reliance on interactive HTTP signatures in concern #2 also excludes migration from a dead server altogether, severely degrading user experience when servers go offline.
For example, a typical current Move Actor implementation (such as the one implemented by Mastodon, or specified in FEP-7628) only assists the user with automatically migrating their Followers list (by setting up a bi-directional alsoKnownAs links and sending the Move activity to all of their followers). In addition, some implementations allow the manual export and re-import of the Following lists, mutes, blocks and bookmarks. However, important content like posts, media attachments, reactions, reposts and so on, does not get automatically transferred.
This FEP proposes a roadmap (a set of steps, design concepts, and recommendations) that enables more complete live migrations, as well as enabling migrations from dead server backups.
"},{"location":"fep/7952/#roadmap-for-portability","title":"Roadmap For Portability","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this specification are to be interpreted as described in RFC-2119.
"},{"location":"fep/7952/#1-unbundle-the-services-and-concerns-of-a-typical-instance","title":"1. Unbundle the services and concerns of a typical instance","text":"Separating the object storage provider from the Actor profile hosting provider enables the implementation of Object IDs that can remain unchanged through migrations and export/import cycles. Specifically, we're looking for Object IDs that remain unchanged even after migrating the objects to be hosted at a different storage provider.
There are several methods of achieving these types of portable IDs, including:
ap:// URI scheme.https URLs that are redirected to their current location), specified in FEP-e3e9: Actor-Relative URLsAs long as a user's identity provider's web domain remains constant, they can migrate to different inbox/outbox and storage providers, without breaking portable links. That is, users will be able to switch hosting and service providers to different domains, without breaking signatures, replies and threading, likes, following and followers, or anything else.
This means that:
https://alice-personal-site.example or https://example.com/users/alice) can now also be blocked/moderated or accrue reputational weightinghttps://storage-provider.example) might also be worth tracking separately for some moderation or spam-/sybil-protection use-cases.Exporting an account based on this model segments neatly and brings with it verifiability, if each object was separately signed by a key published in the still-live Actor object. Importing from such a file is trivial to implement compared to today's account migrations that need to be aware of the data structures and addressing schemes of the previous server.
"},{"location":"fep/7952/#4-enable-verifiable-actor-url-migration","title":"4. Enable verifiable Actor URL migration","text":"If the user wants to move their Actor to a different domain, or switch identity hosting services, a future FEP could define this in a simple, Verifiable Credential-style data model containing attestations and proofs that the new actor is equivalent to the old, controlled by the same subject, etc. This would generalize and standardize one currently thorny requirement of interoperable migration, that of authenticating migration requests.
Note: one possible extension of this authentication artefact would be to incorporate \"pre-rotation\" style commitments of the sort used in distributed key management.
"},{"location":"fep/7952/#end-result-stable-migration-aware-distributed-data","title":"End Result: Stable, migration-aware distributed data","text":"Taken together, these items allow for painless automated migration between service providers, without broken links or effort duplication, and enables restoration from backup and thus migration from dead servers.
"},{"location":"fep/7952/#examples","title":"Examples","text":""},{"location":"fep/7952/#example-1-standalone-activitypub-actor-profile","title":"Example 1: Standalone ActivityPub Actor Profile","text":"Hosted at https://alice-personal-site.example/actor
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/did/v1\",\n \"https://w3id.org/security/multikey/v1\"\n ],\n \"id\": \"https://alice-personal-site.example/actor\",\n \"type\": \"Person\",\n \"name\": \"Alice\",\n \"preferredUsername\": \"alice\",\n \"inbox\": \"https://service-provider.example/users/alice/inbox\",\n \"outbox\": \"https://service-provider.example/users/alice/outbox\",\n \"assertionMethod\": [\n {\n \"id\": \"https://alice-personal-site.example/actor#ed25519-key\",\n \"type\": \"Multikey\",\n \"controller\": \"https://alice-personal-site.example/actor\",\n \"publicKeyMultibase\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n }\n ],\n \"service\": [{\n \"id\": \"https://alice-personal-site.example/actor#storage\",\n \"serviceEndpoint\": \"https://storage-provider.example/users/1234\"\n }]\n}\n Things to note:
https://alice-personal-site.example/actor, but the Inbox and Outbox services are hosted separately, at https://service-provider.example/users/alice/inboxassertionMethod section, as defined in FEP-521a.service section, which sets up the ability to use Actor-Relative URLs for mapping (and thus routing) all requests to their current locations at service providers (see FEP-e939).FEP-e939: Actor-Relative URLs
Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/7d8c/","title":"FEP-7d8c: Documentation: Automation of FEP","text":"Authors Status Date received Tracking issue Discussions Repository Helge @helge@mymath.rocksDRAFT 2025-01-20 #470 Discussions codeberg"},{"location":"fep/7d8c/#summary","title":"Summary","text":"This FEP discusses scripts and woodpecker configuration used to automate parts of the FEP process. The FEP process is described in FEP-a4ed.
As FEP-a4ed, this is a living document, and should be updated as the FEP process evolves.
"},{"location":"fep/7d8c/#vision","title":"Vision","text":"Automation has the goal of reducing the burden on facilitators by:
Once one has simplified this job, one can seek broader adoption of the FEP process..
"},{"location":"fep/7d8c/#todos","title":"Todos","text":"This is a living document. So let's have a todo list
The first two items have been on my personal todo list for a long time, and might stay there ... so feel free to pick them up.
"},{"location":"fep/7d8c/#automation","title":"Automation","text":"Automation for the FEP repository uses the codeberg woodpecker. There are two jobs:
README.md file and creates the tracking issueAutomation code is written in python and is contained in the script folder.
"},{"location":"fep/7d8c/#scripts-and-tests","title":"Scripts and tests","text":"FEP uses python scripts to manage some aspects of it. These should be runnable without installing packages. In order to ensure these scripts stay working, include them in .woodpecker/test.yml
"},{"location":"fep/7d8c/#configuration-variables","title":"Configuration variables","text":"To be able to do these configuration tasks, you need to be in the admin group. See the first discussion here.
The configuration .woodpecker/readme.yml uses two secrets:
Furthermore, the environment variable CI_REPO_CLONE_SSH_URL is used see woodpecker documentation.
We will now discuss how to obtain these secrets.
"},{"location":"fep/7d8c/#the-api-token","title":"The api token","text":"API tokens can be generated on codeberg under user settings -> applications -> select permissions -> issue: Read and Write and giving it a name. They are hexstrings with 40 characters (if I counted correctly), e.g.
33104dd6847e87ef3d6925effdfc852349233034\n"},{"location":"fep/7d8c/#deploy-key","title":"Deploy key","text":"As already mentioned the deploy key is an ssh key. One can generate these on linux via
ssh-keygen -t ed25519 -f deploy_key\n Do not specify a passphrase. Examples:
$ cat deploy_key\n-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\nQyNTUxOQAAACCQ5XqPCdvHPHsukhPS+RMDpMnOCCLW0csGGoFgijQRiwAAAJDpM7fx6TO3\n8QAAAAtzc2gtZWQyNTUxOQAAACCQ5XqPCdvHPHsukhPS+RMDpMnOCCLW0csGGoFgijQRiw\nAAAEB2ofBDBwUcZs5FHsaFMvyO95Qrvn8+PO3BchraucMRwJDleo8J28c8ey6SE9L5EwOk\nyc4IItbRywYagWCKNBGLAAAADWhlbGdlQHJhaW5ib3c=\n-----END OPENSSH PRIVATE KEY-----\n$ cat deploy_key.pub\nssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJDleo8J28c8ey6SE9L5EwOkyc4IItbRywYagWCKNBGL helge@rainbow\n The public key deploy_key.pub should be added to the codeberg repo under settings -> deploy keys -> add deploy key and checking the enable write access checkbox.
See here.
"},{"location":"fep/7d8c/#adding-the-secrets-to-woodpecker","title":"Adding the secrets to woodpecker","text":"One can add these secrets on woodpecker, then selecting the settings wheel -> secrets -> add secret. The secrets only need to available for the push action (corresponding to a merged pull request).
The deploy_key needs to be end with a new line when pasting into the woodpecker ui.
"},{"location":"fep/7d8c/#development","title":"Development","text":"The tools are located in the scripts folder, with the scripts/fep_tools folder containing the python package fep_tools. Most of this tooling only uses python, e.g. scripts/new_proposal.py and scripts/create_readme.md, so they can be run from any environment.
To run the tests, one needs pytest. We note that the tests run from the base folder. The following commands demonstrate how to setup a virtual environment, install the dependencies, and run the tests.
python -mvenv .venv\npip install ./scripts\npytest\n One can leave the venv, by running deactivate.
To build the website, first install the dependencies
python -mvenv .venv\npip install ./scripts\n./scripts/create_site_pages.py\n Then run mkdocs via
mkdocs serve --config-file scripts/mkdocs.yml\n References:
See website.yml.
"},{"location":"fep/7d8c/#changes","title":"Changes","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/82f6/","title":"FEP-82f6: Actor statuses","text":"Authors Status Date received Tracking issue Discussions Repository Gregory Klyushnikov activitypub@grishka.meDRAFT 2025-05-12 #600 Discussions codeberg"},{"location":"fep/82f6/#summary","title":"Summary","text":"This proposal describes an ActivityPub extension to allow actors to publish a short status text, with optional expiration, link attachment, and history.
Some centralized communication services provide their users with the ability to set a status on their account, which is usually displayed on their profile and sometimes next to their name in other places in the UI. These are distinct from regular posts because they can not be interacted with in any way whatsoever, can't contain media attachments, and usually have a short character limit on the order of several hundred characters at most. Statuses are always visible to anyone who can see the actor itself.
"},{"location":"fep/82f6/#requirements","title":"Requirements","text":"The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].
"},{"location":"fep/82f6/#representation-of-statuses","title":"Representation of statuses","text":"A status object is of type ActorStatus, which extends the ActivityPub Object. It has the following fields:
attributedTo: the ID of the actor whose status this is. REQUIRED.content: the plain text content of the status. REQUIRED.published: the timestamp when this status update was created. REQUIRED.id: a unique identifier for this status update. REQUIRED.endTime: a timestamp when this status expires. OPTIONAL. If present, an implementation MUST check this field and, if it is in the past, MUST NOT display this status on the actor's profile and in any other places where statuses are displayed.attachment: an object that contains any extra information about what the actor is currently doing, e.g. a song they're listening to or a video game they're playing. OPTIONAL. If present, content MUST contain a fallback human-readable plain text representation of this object. For example, for a song, it may simply be \"Artist \u2014 Title\".The following two OPTIONAL fields are added to actors:
status: the last, or current, status update of this actor. MUST NOT be present if the last status has expired. MUST contain an inlined ActorStatus.statusHistory: a collection of all past status updates of this actor. If present, implementations MAY provide a UI to view this actor's status history. If absent, implementations SHOULD NOT store this actor's past status updates and MUST NOT expose them in the UI.A status update is created by sending a Create{ActorStatus} activity to followers. Upon receiving this activity, an implementation updates the actor's latest status, and, if it has the statusHistory collection, adds it to the history. After sending this activity, the status field of the actor object MUST be updated with this new status. An Update{Actor} MUST NOT be sent because the Create already implicitly updates that field in the copies of this actor stored on remote servers.
If the actor does not have a statusHistory, the previous status is considered no longer existing, as if it was Deleted.
The latest status is cleared by sending a Remove{ActorStatus} activity to followers. Upon receiving this activity, if the ID of the object matches the actor's current status, an implementation removes the actor's current status. If the actor has the statusHistory collection, it keeps it there. Same considerations for status apply here.
A status is completely deleted by sending a Delete{ActorStatus} activity to followers. If the actor has the statusHistory collection, the status is removed from there as well as being cleared from the profile. Otherwise, this is activity is identical to Remove.
Statuses can not be interacted with. Therefore, implementations SHOULD NOT send any activities that refer to statuses as their object attributed to actors other than the actor that owns this status. Implementations MUST either ignore any such activities by returning a 2xx response, or reject them by returning a 4xx.
Once published, a status object can not be Updated.
The JSON-LD context for this proposal is as follows:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sm\": \"http://smithereen.software/ns#\",\n \"ActorStatus\": \"sm:ActorStatus\",\n \"status\": {\n \"@type\": \"@id\",\n \"@id\": \"sm:status\"\n },\n \"statusHistory\": {\n \"@type\": \"@id\",\n \"@id\": \"sm:statusHistory\"\n }\n }\n ]\n}\n"},{"location":"fep/82f6/#character-limit","title":"Character limit","text":"Implementations MUST support statuses of up to 100 characters or emoji. Implementations MAY allow statuses that are longer, but they MAY be truncated on the receiving side depending on display and storage constraints. It is RECOMMENDED to apply a 100-character limit to the text field where the user enters their status, but be more permissive when processing status updates from other servers.
"},{"location":"fep/82f6/#ui-examples","title":"UI examples","text":"This section is non-normative.
This section shows examples of how implementations might display status updates to users.
Simple UI without history, similar to Discord or GitHub:
Updating one's status, with the ability to set it to expire:
UI with history, similar to early Facebook:
"},{"location":"fep/82f6/#example-status-object","title":"Example status object","text":"{\n \"type\": \"ActorStatus\",\n \"id\": \"https://example.social/users/1/statuses/1747286633\",\n \"attributedTo\": \"https://example.social/users/1\",\n \"content\": \"is desperately trying to bring the old internet back\",\n \"published\": \"2025-05-15T05:23:53.539Z\",\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sm\": \"http://smithereen.software/ns#\",\n \"ActorStatus\": \"sm:ActorStatus\"\n }\n ]\n}\n"},{"location":"fep/82f6/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/844e/","title":"FEP-844e: Capability discovery","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2025-06-14 #624 Discussions codeberg"},{"location":"fep/844e/#summary","title":"Summary","text":"Capability discovery for ActivityPub applications.
This document is based on the idea described in FEP-aaa3: Listing Implemented Specifications on the Application Actor.
"},{"location":"fep/844e/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/844e/#application-object","title":"Application object","text":"An application can advertise its capabilities using the implements property on an Application object.
This object might not be an actor. The value of implements property MUST be an array of Link objects, each containing the following properties:
href (REQUIRED): the unique identifier of the capability. The value MUST be an URI.name (RECOMMENDED): the short description of the capability.Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/844e\"\n ],\n \"type\": \"Application\",\n \"id\": \"https://social.example/server\",\n \"implements\": [\n {\n \"href\": \"https://datatracker.ietf.org/doc/html/rfc9421\",\n \"name\": \"RFC-9421: HTTP Message Signatures\"\n }\n ]\n}\n"},{"location":"fep/844e/#discovery-through-an-actor","title":"Discovery through an actor","text":"An Application object can be linked to an actor using the generator property. The value of this property SHOULD be a partial object containing the implements property. That object MAY be anonymous (without an identifier).
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/844e\"\n ],\n \"id\": \"https://social.example/actors/1\",\n \"type\": \"Person\",\n \"inbox\": \"https://social.example/actors/1/inbox\",\n \"outbox\": \"https://social.example/actors/1/outbox\",\n \"generator\": {\n \"type\": \"Application\",\n \"implements\": [\n {\n \"href\": \"https://datatracker.ietf.org/doc/html/rfc9421\",\n \"name\": \"RFC-9421: HTTP Message Signatures\"\n }\n ]\n }\n}\n"},{"location":"fep/844e/#discovery-through-a-well-known-endpoint","title":"Discovery through a well-known endpoint","text":"The mechanism of discovering an Application object using WebFinger is described in FEP-d556.
Capability discovery through NodeInfo endpoint is discouraged.
"},{"location":"fep/844e/#intended-use","title":"Intended use","text":"The reliance on the mechanism described in this document might increase implementation complexity and hinder interoperability.
Implementers MUST NOT use it in cases where capabilities can be inferred from properties and types of objects.
"},{"location":"fep/844e/#registry-of-software-capabilities","title":"Registry of software capabilities","text":"(This section is non-normative.)
Name Identifier RFC-9421: HTTP Message Signatures https://datatracker.ietf.org/doc/html/rfc9421 RFC-9421 signatures using the Ed25519 algorithm https://datatracker.ietf.org/doc/html/rfc9421#name-eddsa-using-curve-edwards25"},{"location":"fep/844e/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/888d/","title":"FEP-888d: Using https://w3id.org/fep as a base for FEP-specific namespaces","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT 2023-04-10 #83 Discussions codeberg"},{"location":"fep/888d/#summary","title":"Summary","text":"It is considered best practice in the linked-data ecosystem to have IRIs be HTTPS URIs that resolve to a definition of the term being used, and it is desirable to define such terms in a JSON-LD context file that is referenced by its IRI rather than having the full @context object embedded in every single document. ActivityStreams 2.0 and ActivityPub do this with the normative context and namespace provided at https://www.w3.org/ns/activitystreams, but this namespace is not generally open to extensions or to experimental terms. This FEP therefore proposes using https://w3id.org/fep as a base IRI for the FEP process, allowing sub-namespaces for each FEP.
(This section is non-normative.)
The core idea behind this FEP is attributed to helge on SocialHub 1. Specifically, the proposal to register fep at the W3ID service is adopted wholesale, with alterations made to the specifics of implementing the redirect mappings using .htaccess rules. These alterations are intended to allow easier sub-namespace allocation for each FEP.
The key words \"MUST\", \"SHOULD\", \"MAY\" are to be interpreted as described in RFC-2119.
"},{"location":"fep/888d/#introduction","title":"Introduction","text":"(This section is non-normative.)
The Resource Description Framework (RDF), of which JSON-LD is a serialization, uses URIs to identify nodes on a graph, define properties of those nodes, and create relationships between those nodes. Each statement in RDF represents a fact that is constructed by linking a subject to an object with a predicate; for example, in the statement \"Alice knows Bob\", the subject Alice is related to the object Bob by the predicate knows. To avoid ambiguity, we can specify a URI for what it means to \"know\" someone. Such a URI represents a named property or named predicate, and it exists within a namespace, often associated with some ontology or vocabulary. ActivityStreams 2.0 provides and defines the Activity Vocabulary at https://www.w3.org/ns/activitystreams, and terms defined within may use either the base IRI https://www.w3.org/ns/activitystreams# or the compact IRI as:.
For example, we can consider the definition of \"Public\" addressing within ActivityPub, represented by the Public magic collection. When the normative ActivityStreams 2.0 context is applied, the IRI for this collection may be equivalently expressed as Public, as:Public, or https://www.w3.org/ns/activitystreams#Public.
Historically, extension terms within early ActivityPub implementations assumed that those extension terms would be readily adopted within the ActivityStreams namespace, but this did not happen. Currently, terms such as Hashtag, manuallyApprovesFollowers, movedTo, and sensitive are manually defined with compact IRIs using the as: prefix, in effect making it so that any implementation that wishes to understand these properties and types must manually define those terms as well, in the exact same way.
Later extension terms were defined within vendor-specific namespaces such as http://joinmastodon.org/ns or https://joinpeertube.org/ns. With the increased adoption of ActivityPub by software projects and the diverse needs of such projects, each project will often define its own vendor-specific namespace to contain its own terms. This has led to a multitude of namespaces and extension terms, which must be cherry-picked as needed by JSON-LD consumers wishing to maintain compatibility. Additionally, some of these terms are defined incorrectly within @context, leading to the necessity of compatibility hacks on a per-project basis. Even terms defined correctly may overlap with other terms, and proposed new terms must be parented within a vendor namespace, creating the potential for conflict on which vendor should adopt which term.
The aim of this FEP is to provide a vendor-independent namespace under which extension terms can be defined pursuant to the FEP process and the above problems can be reduced.
"},{"location":"fep/888d/#prior-art","title":"Prior art","text":"(This section is non-normative.)
Within the XMPP ecosystem, the core XMPP specification is defined within an RFC, and further functionality is afforded by the \"eXtensible\" nature of XMPP. The XMPP Standards Foundation (XSF) maintains the process for stewarding new extensions via XMPP Extension Protocols (XEPs). XML namespacing for such extensions is provided by urn:xmpp:, as the XML ecosystem generally prefers using URNs rather than using HTTPS URIs. Such URNs are fully location-independent and not vulnerable to DNS expiry, lapsing, or insolvency. Instead, they are assigned within the authority of the XSF. The XSF maintains an XMPP Registrar and allows XEPs to request and define sub-namespaces beneath urn:xmpp:. In exchange, these URNs are not generally dereferencable without a resolver that looks up the URN within the XEP database.
Within the RDF and linked-data ecosystems, there is a strong preference for HTTP or HTTPS URIs, as these can usually be dereferenced via the HTTP protocol for additional information about the subject of the URI. In cases where the URI does not resolve, the URI serves as an identifier not much different than a URN, but with its authority derived from DNS domain rather than from some organizational authority. The reliance on DNS domain creates an issue where the primary domain associated with a group or organization might change. If a previously-used domain is reassigned to a different party, then the new party can mint URIs that accidentally or intentionally conflict with previously-assigned URIs.
To mitigate the DNS authority reassignment issue, trusted intermediary services can maintain a \"persistent URL\" (PURL) service, which allows assigning identifiers on the intermediary domain that will redirect to some other URI. This layer of indirection allows changing the location of the resource by simply changing the redirect's target. W3ID is one such service, operated by the W3C Permanent Identifier Community Group and available at https://w3id.org. At the time of writing this FEP, top-level directory names can be claimed by individuals who submit pull requests to the w3id.org repository on GitHub, and .htaccess files allow redirection based on rewrite rules that transform incoming requests to some other target.
"},{"location":"fep/888d/#specification","title":"Specification","text":""},{"location":"fep/888d/#design-goals","title":"Design goals","text":"Broad design goals for the redirect mapping include:
Accept: application/ld+json HTTP header, and SHOULD otherwise return human-friendly proposal documents or term definitions by default.At minimum, the following redirects SHOULD resolve as follows:
https://w3id.org/fepAccept: * => the FEP repository or current home pagehttps://w3id.org/fep/(:id)Accept: application/ld+json => a specific FEP's context documentAccept: * => a specific FEP's proposal documentAdditionally, the following MAY resolve:
https://w3id.org/fep/(:id)/(:term)Accept: application/ld+json => a specific FEP's specific term definition in JSON-LD ontology/schemaAccept: application/rdf+xml => a specific FEP's specific term definition in RDF/XML ontology/schemaAccept: text/turtle => a specific FEP's specific term definition in Turtle ontology/schemaAccept: * => a specific FEP's specific term's folderAt the time of writing this FEP, the Codeberg repository at https://codeberg.org/fediverse/fep is used to host FEP-related files, and can similarly be used to host context documents.
(This section is non-normative.)
An example .htaccess file is co-located with this FEP, and reproduced below for convenience:
RewriteEngine on\n\n\n# catch root request\nRewriteRule ^\\/?$ https://codeberg.org/fediverse/fep [R=302,L]\n\n\n\n# Catch FEP documents\n\n## By content negotiation\n\n### JSON-LD\nRewriteCond %{HTTP_ACCEPT} application/ld\\+json\nRewriteRule ^([A-Za-z0-9]+)\\/?$ https://raw.codeberg.page/fediverse/fep/fep/$1/fep-$1.jsonld [R=302,L]\n\n### RDF+XML\nRewriteCond %{HTTP_ACCEPT} application/rdf\\+xml\nRewriteRule ^([A-Za-z0-9]+)\\/?$ https://fediverse.codeberg.page/fep/fep/$1/fep-$1.rdf [R=302,L]\n\n### Turtle\nRewriteCond %{HTTP_ACCEPT} text/turtle\nRewriteRule ^([A-Za-z0-9]+)\\/?$ https://fediverse.codeberg.page/fep/fep/$1/fep-$1.ttl [R=302,L]\n\n## By URL hacking\nRewriteRule ^([A-Za-z0-9]+).jsonld$ https://raw.codeberg.page/fediverse/fep/fep/$1/fep-$1.jsonld [R=302,L]\nRewriteRule ^([A-Za-z0-9]+).rdf$ https://raw.codeberg.page/fediverse/fep/fep/$1/fep-$1.rdf [R=302,L]\nRewriteRule ^([A-Za-z0-9]+).ttl$ https://raw.codeberg.page/fediverse/fep/fep/$1/fep-$1.ttl [R=302,L]\n\n## By default, take you to the FEP document\nRewriteRule ^([A-Za-z0-9]+)\\/?$ https://codeberg.org/fediverse/fep/src/branch/main/fep/$1/fep-$1.md [R=302,L]\n\n\n\n# Catch term definitions/schemas/ontologies\n\n## By content negotiation\n\n### JSON-LD\nRewriteCond %{HTTP_ACCEPT} application/ld\\+json\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?)\\/?$ https://raw.codeberg.page/fediverse/fep/fep/$1/$2/$2.jsonld [R=302,L]\n\n### RDF+XML\nRewriteCond %{HTTP_ACCEPT} application/rdf\\+xml\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?)\\/?$ https://fediverse.codeberg.page/fep/fep/$1/$2/$2.rdf [R=302,L]\n\n### Turtle\nRewriteCond %{HTTP_ACCEPT} text/turtle\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?)\\/?$ https://fediverse.codeberg.page/fep/fep/$1/$2/$2.ttl [R=302,L]\n\n### test html\nRewriteCond %{HTTP_ACCEPT} ^text/html$\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?)\\/?$ https://fediverse.codeberg.page/fep/fep/$1/$2/$2.html [R=302,L]\n\n## By URL hacking\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?).jsonld$ https://raw.codeberg.page/fediverse/fep/fep/$1/$2/$2.jsonld [R=302,L]\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?).rdf$ https://fediverse.codeberg.page/fep/fep/$1/$2/$2.rdf [R=302,L]\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?).ttl$ https://fediverse.codeberg.page/fep/fep/$1/$2/$2.ttl [R=302,L]\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?).html$ https://fediverse.codeberg.page/fep/fep/$1/$2/$2.html [R=302,L]\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?).md$ https://fediverse.codeberg.page/fep/fep/$1/$2/README.md [R=302,L]\n\n## By default, just take you to the term's folder\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?)\\/?$ https://codeberg.org/fediverse/fep/src/branch/main/fep/$1/$2 [R=302,L]\n\n\n\n# a generic catch-all rule\nRewriteRule ^(.*)\\/?$ https://codeberg.org/fediverse/fep/raw/branch/main/fep/$1 [R=302,L]\n"},{"location":"fep/888d/#defining-terms-associated-with-an-fep","title":"Defining terms associated with an FEP","text":"FEPs that wish to define extension terms within the w3id.org/fep namespace MUST provide a JSON-LD document co-located within their FEP folder with a filename of the form fep-xxxx.jsonld, where xxxx is the FEP's slug. This document MUST include at least a @context key, whose value is a JSON object containing term definitions. Simple term definitions map a term key to an IRI value. Expanded term definitions contain @id for each term, with @type of @id if the term links to another node on the graph. Refer to LD-TERM-DFN for additional guidance on defining terms within JSON-LD.
The document MAY include additional metadata outside of the @context, such as schema or ontology definitions. If this is done, then you SHOULD NOT include any prefixes in your @context that are only used within the graph, as these can pollute the usage as a context document.
Once the FEP is marked FINAL, the context document MAY be cached forever if referenced. FEPs that define extension terms MAY instead define extension terms within a vendor-specific namespace, but generally this SHOULD NOT be done.
(This section is non-normative.)
For example, say we wanted to define the following terms within the current FEP-888d:
SomeType is a term for some typeexampleA is a term for a property with some literal value (string, boolean, number)exampleB is a term for a property that links to another node on the graph (for example, another object)exampleC is a term for a property that is an ordered list of literal values that are specifically non-negative integersThe context document might look like this, at minimum:
{\n \"@context\": {\n \"SomeType\": \"https://w3id.org/fep/888d/SomeType\",\n \"exampleA\": \"https://w3id.org/fep/888d/exampleA\",\n \"exampleB\": {\n \"@id\": \"https://w3id.org/fep/888d/exampleB\",\n \"@type\": \"@id\"\n },\n \"exampleC\": {\n \"@id\": \"https://w3id.org/fep/888d/exampleC\",\n \"@type\": \"http://www.w3.org/2001/XMLSchema#nonNegativeInteger\",\n \"@container\": \"@list\"\n }\n }\n}\n Refer to LD-TERM-DFN for additional guidance on defining terms within JSON-LD.
A folder within the FEP's directory should be used to provide additional documentation for the term, such as ontology or schema definitions via JSON-LD, RDF/XML, and/or Turtle.
"},{"location":"fep/888d/#example-using-fragment-identifiers","title":"Example using fragment identifiers","text":"(This section is non-normative.)
Depending on convenience or preference, the context document might instead look like this:
{\n \"@context\": {\n \"SomeType\": \"https://w3id.org/fep/888d#SomeType\",\n \"exampleA\": \"https://w3id.org/fep/888d#exampleA\",\n \"exampleB\": {\n \"@id\": \"https://w3id.org/fep/888d#exampleB\",\n \"@type\": \"@id\"\n },\n \"exampleC\": {\n \"@id\": \"https://w3id.org/fep/888d#exampleC\",\n \"@type\": \"http://www.w3.org/2001/XMLSchema#nonNegativeInteger\",\n \"@container\": \"@list\"\n }\n }\n}\n Refer to LD-TERM-DFN for additional guidance on defining terms within JSON-LD.
In such a case, the FEP document should include an element with an HTML identifier that exactly matches the term name, so that the IRI fragment resolves properly. In practice, this means one of the following:
summary, acknowledgements, requirements, references, copyright, and so on, including any headers that the FEP author includes for purposes other than explicitly defining the term.### h3 {#custom-identifier} and render <h3 id=\"custom-identifier\">h3</h3>. Markdown specifications such as CommonMark currently do not support custom attributes, but some Markdown processors such as Goldmark support custom attributes on headers (but not on arbitrary elements). See CM-ATTRS for more discussion of this feature.id attributes exactly matching the term name. HTML within Markdown files is generally rendered as-is, although it may be sanitized, stripped, or disallowed for security purposes. In cases where it is allowed, however, it can be an effective way to express term definitions within an FEP document.(This section is non-normative.)
If terms are defined within HTML using RDFa, and this HTML is embedded in the Markdown contents of the FEP document, then these term definitions can be used to programmatically generate machine-readable term definitions, schemas, and ontologies in multiple formats. A Python script is provided within the FEP repository at scripts/make_definitions.py for convenience.
To make use of the script, it is necessary to structure term definitions in a specific way. We define a \"term definition\" to be any HTML element that adheres to the following requirements:
id attribute equal to the shorthand term name.resource attribute equal to the term's full IRI.Generally, this HTML element should be a <section> tag, and it should contain a definition list (<dl></dl>) where each term (<dt></dt>) and definition (<dd></dd>) provide a key-value pair representing a property of that term definition. Examples may be provided within a preformatted code block (<pre><code></code></pre>), and multiple examples may be provided. Example code blocks can have a title attribute on the <pre> tag. It is generally recommended to put any term definitions below a heading called something like \"Terms defined\".
For all term definitions regardless of type, it is recommended to include the following properties in your definition list:
rdfs:label is a natural language label that can replace the shorthand term. For example, a term that is normally expressed with the shorthand hasValue might be labelled has value in plain English.rdfs:comment is a natural language description that gives more detail about what the term means.rdfs:isDefinedBy is a reference pointing to where the term is defined. Usually this is the current FEP, but meta-FEPs that bundle other FEPs may use this property to refer to the original FEP that defined a term.For term definitions that define a class (such as rdfs:Class or owl:Class), it is recommended to include the following properties in your definition list if appropriate:
rdfs:seeAlso is a reference to some other resource that provides more information about the current term. Usually this can be used to link a class to the properties that instances of that class may have.rdfs:subClassOf is a property that declares the current class to inherit from the object. Logically, if something is an instance of the current class, it is also an instance of the superclass. For example, in AS2-Vocab, OrderedCollection is a subclass of Collection, so all OrderedCollection objects are also implied to have a type of Collection as well, even if not explicitly declared to be one.For term definitions that define a property (such as rdf:Property, owl:DatatypeProperty, or owl:ObjectProperty), it is recommended to include the following properties in your definition list if appropriate:
rdfs:domain is the Domain of the property. Logically, this implies that if a property p has a domain D, and a given resource has the property p, then that resource has a type of D. For example, in AS2-Vocab the domain of actor is Activity, so anything that has an actor is implied to be an Activity even if not explicitly declared to be one.rdfs:range is the Range of the property. Logically, this implies that if a property p has a range R, and a given resource is the value of p, then that resource has a type of R. For example, in AS2-Vocab the range of replies is Collection, so anything that is the value of replies is implied to be a Collection even if not explicitly declared to be one.property of owl:minCardinality has a content of 1 (with a datatype of xsd:nonNegativeInteger). In other words, a property with a minimum cardinality of 1 must have at least one value. If the property is not Required, then this statement can be amended to have a content of 0, or in other words, you are stating that this property must have at least 0 values (which allows having no values).property of owl:maxCardinality has a content of 1 (with a datatype of xsd:nonNegativeInteger). In other words, a property with a maximum cardinality of 1 can have at most one value. If the property is not Functional, then this statement can be omitted.rdfs:seeAlso is a reference to some other resource that provides more information about the current term. Usually this can be used to link a property to other related terms, such as term definitions that can be used as the value of this property.If the term definition contains JSON-LD keywords like @id, @vocab, @set, or @list within a <dd> element whose corresponding <dt> element's inner text exactly matches Range, then these keywords can be used to automatically generate a context document. Set the attribute excluded on the term definition to a non-empty value to exclude it from auto-generated context term definitions.
Source code for the script is available in make_definition.py, currently co-located in the FEP-888d folder.
The script will operate according to the following logic:
"},{"location":"fep/888d/#options-available","title":"Options available","text":"@id, @vocab, @set, and @list.fep-$SLUG.jsonld co-located within the FEP folder). Failure to read this path as valid JSON will lead to an empty context mapping.id and resource. These are considered \"term definitions\".s equal to the resource attribute of the element.term_type equal to the typeof attribute of the element.s rdf:type term_type.property attribute. These are considered \"property definitions\".p equal to the property attribute.o equal to the resource attribute. If not present, then set the object o equal to the href attribute. If not present, then set the object o equal to the content attribute. If not present, then set the object o equal to the inner text of the current element.lang equal to the lang attribute.datatype equal to the datatype attribute.s, p, o, lang, and datatype, making sure to expand any CURIEs according to the RDFa initial context.datatype or lang, then the object o is a Literal.o is an IRI reference.excluded attribute, then extract JSON-LD keywords and automatically generate a context term definition.term_name equal to the id attribute of the element.options mapping to keep track of extended term definitions.<dt> element with its corresponding child <dd> element.<dt> element whose inner text is exactly \"Range\". [TODO: something less fragile?]<dd> element for a resource attribute. If there is one, extract this IRI as type_iri.type_iri starts with xsd, then insert @type: type_iri into the options mapping.<dd> element for a substring @id. If found, then insert @type: @id into the options mapping.<dd> element for a substring @vocab. If found, then insert @type: @vocab into the options mapping.<dd> element for a substring @set. If found, then insert @container: @set into the options mapping.<dd> element for a substring @list. If found, then insert @container: @list into the options mapping.options mapping is not empty, then insert @id: s into the options mapping.options mapping is not empty, then map term_name to the options mapping. Otherwise, map term_name to the string s. This is considered a \"context term definition\".s is not a fragment identifier, then for each alternate output format in OUTPUT_FORMATS, serialize the current term graph to that format and write it to a file within a co-located subfolder with the name term_name.term_name set equal to the id attribute of the term definition.<dt> element with its corresponding child <dd> element.<dt> and <dd> element, write a definition list term and definition list definition.<pre>.Examples.title attribute is present, then write a paragraph with the text set to the value of title.lang attribute is present, then append its value immediately afterward.fep-$SLUG.$FORMAT. For example, if Turtle output is enabled, then fep-xxxx.ttl will be written. For JSON-LD output, use the current context mapping when serializing the FEP-wide graph. For HTML+RDFa or Markdown output, ignore these formats. (Outputting HTML can be done as part of a separate process of rendering the Markdown with a static site generator. Outputting the Markdown makes no sense since it is the source material and should not be overwritten.)fep-xxxx.jsonld.markdown on PyPI)beautifulsoup4)rdflib)markdownify)python-frontmatter)uv run instead.python fep/888d/make_definitions.py $SLUG will read term definitions from the contents of fep/$SLUG/fep-$SLUG.md, then generate alternate formats for the FEP. If your terms are defined with fragment identifiers (of the form https://w3id.org/fep/xxxx#term), then only the FEP itself will be generated in alternate formats. If your terms are defined with absolute identifiers (of the form https://w3id.org/fep/xxxx/term), then co-located subdirectories will be created as well, and singular term definitions will be generated in chosen output formats. If a context.jsonld document is present in the FEP folder, then it will be copied into the JSON-LD alternate format output. The current default output formats are:python fep/888d/make_definitions.py $SLUG -c will do everything in the above bullet point, except for copying context.jsonld. Instead, it will check for JSON-LD keywords and attempt to auto-generate a context document based on those keywords. If JSON-LD output is disabled, the script will generate only a context document instead of a combined context document and schema or ontology.(This section is non-normative.)
Using the same fictitious terms from the above examples:
SomeType is a term for some typeexampleA is a term for a property with some literal value (string, boolean, number)exampleB is a term for a property that links to another node on the graph (for example, another object)exampleC is a term for a property that is an ordered list of literal values that are specifically non-negative integersExcludedExample is a term for some class that is useful for ontology but not intended to be used for the context mappingWe can formulate the following machine-readable term definition blocks, which are intended to provide a range of examples of various RDFa syntactical constructs for demonstration purposes. Specifically, the following mechanisms are demonstrated:
resource with a typeof some classproperty with a specified langproperty has a value that is a resourceproperty where the object value is referred to by href instead of resource (for example, in an anchor link)property by including multiple tags@context, such as signaling that a property is required or functionalproperty has literal content instead of referring to a resource, and that this content can have its own datatype to which it can be coercedexcluded attributehttps://w3id.org/fep/888d/SomeType Label Some Type Comment Some type. Subclass of Object See also exampleA | exampleB | exampleC Is defined by FEP-888d \n\n{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n\n exampleA URI https://w3id.org/fep/888d/exampleA Label has example relation A with the literal value Comment A property with some literal value (string, boolean, number) Domain SomeType Range A literal value Required Yes Functional Yes Is defined by FEP-888d \n\n{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n\n exampleB URI https://w3id.org/fep/888d/exampleB Label has example relation B with Comment A property that links to another node on the graph (for example, another object) Domain SomeType Range An object (@id) Required No Functional No Is defined by FEP-888d \n\n{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n\n exampleC URI https://w3id.org/fep/888d/exampleC Label has an exampleC value of Comment A property that is an ordered list of literal values that are specifically non-negative integers Domain SomeType Range A list of non-negative integers (@list) Required No Functional Yes Is defined by FEP-888d \n\n{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n\n ExcludedExample URI https://w3id.org/fep/888d#ExcludedExample Label Excluded Example Comment A class that is useful for ontology but not intended to be used for the context mapping Subclass of Object Is defined by FEP-888d"},{"location":"fep/888d/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/888d/SomeType/","title":"SomeType","text":"URI : https://w3id.org/fep/888d/SomeType
Label : Some Type
Comment : Some type.
Subclass of : Object
See also : exampleA | exampleB | exampleC
Is defined by : FEP-888d
"},{"location":"fep/888d/SomeType/#examples","title":"Examples","text":"Example of a SomeType with properties exampleA, exampleB, exampleC
{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n"},{"location":"fep/888d/exampleA/","title":"exampleA","text":"URI : https://w3id.org/fep/888d/exampleA
Label : has example relation A with the literal value
Comment : A property with some literal value (string, boolean, number)
Domain : SomeType
Range : A literal value
Required : Yes
Functional : Yes
Is defined by : FEP-888d
"},{"location":"fep/888d/exampleA/#examples","title":"Examples","text":"Example of a SomeType with properties exampleA, exampleB, exampleC
{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n"},{"location":"fep/888d/exampleB/","title":"exampleB","text":"URI : https://w3id.org/fep/888d/exampleB
Label : has example relation B with
Comment : A property that links to another node on the graph (for example, another object)
Domain : SomeType
Range : An object (@id)
Required : No
Functional : No
Is defined by : FEP-888d
"},{"location":"fep/888d/exampleB/#examples","title":"Examples","text":"Example of a SomeType with properties exampleA, exampleB, exampleC
{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n"},{"location":"fep/888d/exampleC/","title":"exampleC","text":"URI : https://w3id.org/fep/888d/exampleC
Label : has an exampleC value of
Comment : A property that is an ordered list of literal values that are specifically non-negative integers
Domain : SomeType
Range : A list of non-negative integers (@list)
Required : No
Functional : Yes
Is defined by : FEP-888d
"},{"location":"fep/888d/exampleC/#examples","title":"Examples","text":"Example of a SomeType with properties exampleA, exampleB, exampleC
{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n"},{"location":"fep/8967/","title":"FEP-8967: Generating link previews for attached links","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 2025-09-16 #681 Discussions codeberg"},{"location":"fep/8967/#summary","title":"Summary","text":"A common feature in social applications is to show users a rich preview of a link included in the content of a message or post, before the user clicks the link. Currently, applications like Mastodon generate link previews for the first link found in the content, without considering the publisher's possible intent. This FEP allows publishers to explicitly signal which links are intended for special processing, using the existing attachment model. Optionally, publishers can include their own link preview information so that trusting consumers can skip generating their own previews.
"},{"location":"fep/8967/#link-in-attachment","title":"Link in attachment","text":"The attachment model can be used to indicate that something performs a particular function as an extension of the main content. AS2-VOCAB defines attachment as \"semantically similar to attachments in email\" for something that \"potentially requires special handling\".
Publishers MAY include a Link in attachment to signal that this link should be processed semantically as an attached link preview card, appearing similarly to attached images, attached videos, or attached audio. The link attachment MUST have an href, indicating that it is a Link.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"attachment\": {\n \"href\": \"https://foo.example/\"\n }\n}\n Upon encountering an attachment that is a Link, consumers SHOULD show this link as \"attached\" to the object. At minimum, the href can be rendered directly, perhaps alongside an icon representing a link.
Publishers MAY include link preview information using the preview property:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sensitive\": \"as:sensitive\"\n }\n ],\n \"attachment\": {\n \"href\": \"https://foo.example/\",\n \"preview\": {\n \"type\": \"Article\",\n \"name\": \"Example Essay\",\n \"summary\": \"In which some information is provided...\",\n \"image\": {\n \"sensitive\": true,\n \"url\": {\n \"href\": \"https://cover-image.example/file.jpg\",\n \"mediaType\": \"image/jpg\",\n \"width\": 1200,\n \"height\": 630\n }\n },\n \"attributedTo\": {\n \"name\": \"The Author\",\n \"icon\": {\n \"url\": {\n \"href\": \"https://avatar.example/file.png\",\n \"mediaType\": \"image/png\",\n \"width\": 48,\n \"height\": 48\n }\n },\n \"url\": {\n \"href\": \"https://author.example/\",\n }\n }\n }\n }\n}\n The exact form of the preview and its processing model is out of scope of this FEP (as each consumer is free to render information according to their own design language and understanding), but some properties may be useful as equivalents of OpenGraph properties which are widely used for link previews:
name -- similar to og:title, indicate the preview card's primary text.summary -- similar to the og:description, indicate the preview card's secondary text.image -- similar to og:image, indicate the preview card's image.type -- similar to og:type, indicate the type of the target resource. This can be used to select an appropriate icon representing the resource.attributedTo -- loosely similar to article:author, music:musician, music:creator, book:author, and other such properties, indicate the preview card's attribution.name -- the name that should be attributedicon -- the icon that should be displayed alongside the attributed nameurl -- the link that should wrap the attributionConsumers that do not trust the publisher's provided preview information MAY generate their own preview through whichever means they find appropriate, such as for example fetching the link target and extracting OpenGraph information or HTML tags such as <title> or <meta>.
(This section is non-normative.)
"},{"location":"fep/8967/#attaching-objects-directly","title":"Attaching objects directly","text":"Rather than attaching a Link with an optional preview, resources can be attached directly by their id along with arbitrary optional claims.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"attachment\": {\n \"id\": \"https://foo.example/\",\n \"type\": \"Article\",\n \"name\": \"Example Essay\",\n // ...\n }\n}\n The difference between attaching an Object versus attaching a Link is that attaching an Object creates a direct relation between the current object and the attached object, whereas attaching a Link does not create this relation between two objects. For example, consider the difference between attaching an Image versus attaching a Link that targets an image. In most cases, directly attaching the Image is probably more appropriate. However, if the publisher attaches a Link instead, it might be because the link is present in the content and the publisher wishes to indicate this for special processing; for whatever reason, the publisher does not want to directly attach the Image. This depends on the specific details of the processing model, which is out of scope for this FEP.
href as a signal to generate link previews with their existing OpenGraph logic. In the future, Mastodon plans to stop automatically extracting the first link in content, but for now the first link is used as a fallback in case no attachment is present.CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/8a8e/","title":"Index","text":"hide: - navigation description: \"ActivityStreams defines the Object Type Event. In real-world applications, the event object immediately showed the need for extension. Applications featuring Event objects have often chosen to add additional attributes and clarifications (i.e., interpretations) in order to implement their particular use case. This proposal clarifies and extends the ActivityPub standard to address the needs that have arisen in real-world implementations. This includes guidelines for the minimal interoperable event, handling of RSVP (\"r\u00e9pondez s'il vous pla\u00eet\", i.e., attendee management, and side effects), attendee capacities, physical location addresses, virtual locations, timezone, and clarification of how to control the visibility of events in federation. These differences in how the aforementioned features are implemented have led to fragmentation in how events are published, discovered, and managed across platforms.\"
"},{"location":"fep/8a8e/#fep-8a8e-a-common-approach-to-using-the-event-object-type","title":"FEP-8a8e: A common approach to using the Event object type","text":"Authors Status Date received Tracking issue Discussions Repository Andr\u00e9 Menrath andre.menrath@posteo.de, les lesion@autistici.orgDRAFT 2025-04-23 #565 Discussions codeberg"},{"location":"fep/8a8e/#summary","title":"Summary","text":"ActivityStreams defines the Object Type Event. In real-world applications, the event object immediately showed the need for extension. Applications featuring Event objects have often chosen to add additional attributes and clarifications (i.e., interpretations) in order to implement their particular use case. This proposal clarifies and extends the ActivityPub standard to address the needs that have arisen in real-world implementations.
This includes guidelines for the minimal interoperable event, handling of RSVP (\"r\u00e9pondez s'il vous pla\u00eet\", i.e., attendee management, and side effects), attendee capacities, physical location addresses, virtual locations, timezone, and clarification of how to control the visibility of events in federation.
These differences in how the aforementioned features are implemented have led to fragmentation in how events are published, discovered, and managed across platforms.
"},{"location":"fep/8a8e/#what-is-not-covered","title":"What is not covered","text":"We do not cover recurrence, multiple-scheduled, and connected events.
For more information on that, check out for example: - RFC 5545 Section 3.8.5.3 - RFC 5545 Section 3.8.4.5
"},{"location":"fep/8a8e/#history","title":"History","text":"Fediverse applications, such as Mobilizon, Friendica, Gancio, and Hubzilla, have adopted varying approaches for handling ActivityPub events. Their different aims and the varying use cases have led to fragmentation in how events are published, discovered, and managed across federation.
Friendica and Hubzilla, for instance, follow the example set by ActivityStreams 2.0, using Invite for events, which seems to be very suited for smaller, more private gatherings, such as a birthday party. Invite seems natural when visibility is intentionally limited. On the other hand, applications like Mobilizon or Gancio have a focus on announcing public events and therefore chose to make use of the Create activity. Maybe another reason was that using creates eases interoperability with applications that do not support the Invite activity (yet). Furthermore, this may be justified because public events should as well be discoverable by anonymous actors, even by people who do not have an account in the Fediverse. This is achieved through federated event calendars, which are assembled by the instance administrators, who can add various federated event sources by following federated ActivityPub actors.
Within Mobilizon, for historical and internal reasons, the federated visibility of events is currently still controlled by whether a Group has published the Event or not, and whether the Event has also been announced at least once, rather than using to/cc (e.g., like Mastodon's \"public silent\"). In federation with other applications, this and other discrepancies led to issues, which have proven to be huge blockers. That's why this FEP sets out to define a common ground.
The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in RFC-2119.
The context of attributes: Attributes that have no context prefix are to be interpreted as being in the https://www.w3.org/ns/activitystreams context.
The ActivityStreams specification already defines an object type Event which is inheriting all properties from the base Object, so some properties useful for describing an event are already specified, but some others are missing, and an enhanced specification will help.
For an Event at least the following properties are REQUIRED:
name: A simple, human-readable, plain-text title for the event. HTML markup MUST NOT be included. Values for the name MUST NOT be an empty string and the name MAY be expressed using multiple language-tagged values.
startTime: The date and time describing the moment the event began or is scheduled to begin in the format as specified in the Activity Stream 2.0 specification. The startTime is not REQUIRED in case an event is postponed and a https://w3id.org/fep/8a8e/previousStartTime is set instead.
endTime: The date and time when the event ends or is scheduled to end in the format as specified in the Activity Stream 2.0 specification. The endTime MUST be a later date than the startTime. If the event is open-ended, it must include an end time at which applications (not humans) can treat the event as having ended.
organizers
{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/foo\",\n \"name\": \"New years party\",\n \"startTime\": \"2014-12-31T23:00:00Z\",\n \"endTime\": \"2015-01-01T06:00:00Z\",\n \"organizers\": null\n}\n"},{"location":"fep/8a8e/#events-with-open-end","title":"Events with Open End","text":"If the event is open-ended, or the endTime is not intended to be displayed to humans, the https://w3id.org/fep/8a8e/displayEndTime attribute SHOULD be used and set to false.
Receiving an Event with an endTime set and no https://w3id.org/fep/8a8e/displayEndTime set, it MUST be treated as if https://w3id.org/fep/8a8e/displayEndTime is set to true, requiring the endTime to be displayed.
If an Event is received with no endTime set, the Event MUST be treated as if https://w3id.org/fep/8a8e/displayEndTime were set to false to avoid misleading users. In such cases, the receiving application MAY infer an appropriate endTime based on its requirements.
This section is non-normative.
The https://w3id.org/fep/8a8e/timezone property is introduced to support time zone information for events, enabling consistent handling of local \"wall times\".
It is important to note that the \"time-offset\" component in startTime and endTime property does not correlate to time-zones, and while times that include the \"time-offset\" component work well for timestamps, they cannot be reliably converted to and from local \"wall times\" without additional information and processing. https://www.w3.org/TR/activitystreams-core/#dates
For this reason, if the Event is primarily conducted in a single specific time-zone, the application SHOULD set the https://w3id.org/fep/8a8e/timezone.
In case the https://w3id.org/fep/8a8e/timezone property is specified it MUST be a specific IANA time zone identifier included in the IANA Time Zone Database (often called tz or zoneinfo, see https://www.w3.org/TR/timezone/#time-zone-identifiers).
{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/foo\",\n \"name\": \"New years party\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00+01:00\",\n \"endTime\": \"2015-01-01T04:00:00+01:00\",\n \"timezone\": \"Europe/Rome\"\n}\n"},{"location":"fep/8a8e/#location","title":"Location","text":"ActivityStreams provides a flexible framework for representing locations, as outlined in this section of the specification. However, its flexibility can sometimes lead to ambiguity.
This section aims to establish a clear and consistent format for representing the locations of events, whether they are physical, virtual, or mixed.
Rather than creating a new schema, this specification adopts the location property from Schema.org with some adjustments to better suite federated event handling:
Exclusion of Text as a valid type: Unlike the original Schema.org definition, Text is not a permitted type for location and MUST NOT be used. If only a name is required to describe a location, the Place type can be used.
Support for multiple locations: To specify mixed-format events (e.g., hybrid online and offline events), the location property can contain an array of locations. Each entry in the array can represent either a physical Place or a VirtualLocation.
For adding information about a physical location of an Event the Place object within the events location property SHOULD be extended via the attribute address which is of type https://schema.org/address or https://schema.org/Place MAY be used directly.
This allows the address to be given as a simple text input as well as a structured https://schema.org/PostalAddress. Applications MUST be able to transmogrify both incoming address types to their internal representation.
It is RECOMMENDED to always provide geolocation data via longitude and latitude, as filtering by these properties is crucial in federation.
Note Even if users do not provide this data directly, attempting to look up the geolocation of a physical location once on the source side can save a lot of resources caused by multiple lookups on the receivers in federation.
Example with pure text address:
{\n \"@context\": [\n \"https://schema.org\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/new-year-party\",\n \"name\": \"New years party\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00+01:00\",\n \"endTime\": \"2015-01-01T04:00:00+01:00\",\n \"location\": {\n \"type\": \"Place\",\n \"name\": \"Foo Bar\",\n \"address\": \"15 Fediverse Street, 8010 Fediverse Town, Austria\",\n \"latitude\": 47.06829,\n \"longitude\": 15.45634\n }\n}\n Example with address of type https://schema.org/PostalAddress:
{\n \"@context\": [\n \"https://schema.org\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/new-year-party\",\n \"name\": \"New years party\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"location\": {\n \"id\": \"\",\n \"type\": \"Place\",\n \"name\": \"Foo Bar\",\n \"address\": {\n \"type\": \"PostalAddress\",\n \"addressCountry\": \"Austria\",\n \"addressLocality\": \"Fediverse Town\",\n \"addressRegion\": \"Steiermark\",\n \"postalCode\": \"8010\",\n \"streetAddress\": \"15 Fediverse Street\"\n },\n \"latitude\": 47.077892,\n \"longitude\": 15.460744\n }\n}\n"},{"location":"fep/8a8e/#virtual","title":"Virtual","text":"An online Location SHOULD be represented by using a type VirtualLocation as specified in https://schema.org/VirtualLocation.
Applications SHOULD at least set and be able to make use of name and url properties.
{\n \"@context\": [\n \"https://schema.org\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/new-year-party\",\n \"name\": \"Fediverse Moderation Meeting\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"location\": {\n \"type\": \"VirtualLocation\",\n \"name\": \"Jitsi Meet Meeting Link\",\n \"url\": \"https://jitsi.example.org/fediverse-moderation-meeting\"\n }\n}\n"},{"location":"fep/8a8e/#hybrid","title":"Hybrid","text":"Using multiple entries in location makes it easy to define multiple virtual and physical locations.
When not supporting multiple entries in location applications MUST use the first supported item.
{\n \"@context\": [\n \"https://schema.org\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/new-year-party\",\n \"name\": \"Discussion about the Fediverse\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"location\": [\n {\n \"type\": \"Place\",\n \"name\": \"Fediverse University\",\n \"url\": \"https://university.example.com\",\n \"address\": \"15 Fediverse Street, 1337 Fediverse Town, Fediverse Country\"\n },\n {\n \"type\": \"VirtualLocation\",\n \"name\": \"Big Blue Button Participation Link\",\n \"url\": \"https://bbb.example.com/meeting-room-1\"\n },\n {\n \"type\": \"VirtualLocation\",\n \"name\": \"Live Stream\",\n \"url\": \"https://peertube.example.com/fediverse-discussion\"\n }\n ]\n}\n"},{"location":"fep/8a8e/#event-status","title":"Event status","text":"To indicate the status of an Event, the https://w3id.org/fep/8a8e/eventStatus property MUST be used. As a fallback, it is RECOMMENDED to also support the status property, especially for incoming remote events, which might contain a value from https://www.w3.org/2002/12/cal/ical#status.
The possible values for the https://w3id.org/fep/8a8e/eventStatus are: - EventCancelled - EventMovedOnline - EventPostponed - EventRescheduled - EventScheduled - EventTentative
Note https://w3id.org/fep/8a8e/eventStatus is almost identical to https://schema.org/eventStatus, however it has an additional status type for the event being tentative, which is leaned on iCalendar's TENTATIVE.
If the event creator or the event creator's application does not handle joins via ActivityPub it MUST always respond to Join activities with an Ignore response, where the attribute object MUST either contain the ID of the Join activity, or contain a Join object with the target being set to the event-objects ID, where the former is preferred.
This SHOULD be made clear beforehand by setting https://w3id.org/fep/8a8e/joinMode to none.
If an application does not handle joins of an Event via ActivityPub, but knows of an external URL which handles the attendee management https://w3id.org/fep/8a8e/joinMode SHOULD be set to external. If joinMode is set to external, also https://w3id.org/fep/8a8e/externalParticipationUrl SHOULD be set.
Note This is compatible with Mobilizon's definition of joinMode, but it is not identical.
{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/foo\",\n \"name\": \"New years party\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"joinMode\": \"external\",\n \"externalParticipationUrl\": \"https://example.org/foo#register\"\n}\n Note externalParticipationUrl could also be a mailto-URI, which is a way of handling event participants that is still used quite frequently by many organizers.
{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/fediverse-workshop\",\n \"organizers\": null,\n \"name\": \"Fediverse Workshop\",\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2025-01-01T04:00:00-08:00\",\n \"joinMode\": \"external\",\n \"externalParticipationUrl\": \"mailto:fediverse-workshop@example.org\"\n}\n"},{"location":"fep/8a8e/#indicating-visibility-and-target-audience-of-rsvp-requests","title":"Indicating visibility and target audience of RSVP requests","text":"Who the participants of an event are should possibly not be public, but should, for example, only be visible to the organisers of the event, in other cases also to other confirmed participants, or, for example, completely anonymously. An Event SHOULD provide information about the visibility scope of valid Join requests targeting the event via utilizing https://w3id.org/fep/8a8e/requiredJoinVisibility.
To specify the maximum number of attendees for an Event, applications SHOULD use the https://schema.org/maximumAttendeeCapacity.
Remaining attendee capacity SHOULD be shown indirectly via using the Collection https://w3id.org/fep/8a8e/attendees. This is a list of all event attendees. Every Actor that has accepted an Invite containing the Event as an object or which Join targeting the Event has been accepted is added as a side effect. Note that also other object types other than Actor (and it's subclasses) might be part of that collection, e.g. when an application allows for attendees to register by e-mail. In that case using https://schema.org/Person or https://schema.org/Organization is RECOMMENDED. The attendees collection MUST be either an OrderedCollection or a Collection and MAY be filtered by the privileges of an authenticated user, or as appropriate if no authentication is given.
{\n \"@context\": [\n \"https://schema.org\",\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/foo-bar-party\",\n \"name\": \"New years party\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"maximumAttendeeCapacity\": 100,\n \"attendees\": {\n \"type\": \"Collection\",\n \"id\": \"https://example.org/foo-bar-party/attendees\",\n \"totalItems\": 84\n }\n}\n"},{"location":"fep/8a8e/#attendees-collection","title":"Attendees Collection","text":"Every Event MAY have an attendees collection. This is a list of all actors that meet one or more of the following conditions, added as a side effect.
Join activity with this object as the object property that has been answered with an Accept.Invite activity from the Event's owner (where the Event is specified as the object property) with an Accept activity.The attendees collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges of an authenticated user or as appropriate when no authentication is given. This collection MAY have the totalItems set regardless of authentication. If https://schema.org/maximumAttendeeCapacity is used totalItems SHOULD be public to disclose remaining capacity.
If the event has an image in the style of a flyer or a share-pic which is representing the whole event (i.e., is also containing at least the name and start-date in the image) this image SHOULD be set using the image attribute.
If the Event has a particular banner image, this image SHOULD be set as the first attachment of type Image (type Document is also valid but not RECOMMENDED). To indicate that an image from within the attachment attribute of an Event should be preferred over the flyer/share-pic image(s) within image attribute, for the use of list pages and header images, the attribute https://w3id.org/fep/8a8e/isBannerImage SHOULD be set.
Federated event calendars usually have thumbnail images with fixed aspect ratios. Therefore, https://docs.joinmastodon.org/spec/activitypub/#focalPoint SHOULD be set.
Note Providing multiple versions of banner images and specifying width and height gives receiving applications the most flexibility to choose the most appropriate one based on aspect ratio.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\": \"http://joinmastodon.org/ns#\",\n \"focalPoint\": {\n \"@container\": \"@list\",\n \"@id\": \"toot:focalPoint\"\n }\n }\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/new-year-party\",\n \"name\": \"New years party\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"image\": {\n \"type\": \"Image\",\n \"mediaType\": \"image/jpeg\",\n \"url\": \"https://example.com/images/new-year-party-flyer.png\",\n \"focalPoint\": [\n -0.55,\n 0.43\n ]\n }\n}\n"},{"location":"fep/8a8e/#event-categories","title":"Event Categories","text":"Federated event platforms benefit from a shared understanding of general event categories. These categories serve a distinct purpose from user-generated tags or hashtags. While hashtags are typically entered freely and reflect a specific language, event categories are drawn from a limited, generalized set, making them more suitable for tasks like filtering events in aggregated or joined calendars.
To represent categories, this FEP introduces the property https://w3id.org/fep/8a8e/category.
In most cases, a single category should be sufficient. However, multiple categories may be included when relevant. Applications SHOULD utilize at least the following recommended set of event categories:
Note Implementing applications may extend this list or allow user-generated categories. However, such additions may not be understood by all consuming platforms and could default to a generic or \"unknown\" category.
"},{"location":"fep/8a8e/#example-of-an-event-with-a-single-category","title":"Example of an event with a single category","text":"{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/event/1\",\n \"name\": \"Demonstration againt fascism\",\n \"startTime\": \"2014-05-01T12:00:00-08:00\",\n \"startTime\": \"2014-05-01T18:00:00-08:00\",\n \"organizer\": null,\n \"category\": \"MOVEMENTS_POLITICS\"\n}\n"},{"location":"fep/8a8e/#example-of-an-event-with-multiple-categories","title":"Example of an event with multiple categories","text":"{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/open-mic-jam\",\n \"name\": \"Open Mic Jam at Sunny's\",\n \"startTime\": \"2014-12-12T20:00:00-08:00\",\n \"endTime\": \"2014-12-12T23:00:00-08:00\",\n \"organizer\": null,\n \"category\": [\n \"MUSIC\",\n \"CREATIVE_JAM\"\n ]\n}\n"},{"location":"fep/8a8e/#discoverability","title":"Discoverability","text":"Fediverse applications that feature Event objects MAY provide public calendars, search functions or grouped visualization pages (e.g., for tags or categories) that display events from multiple federated sources. In both cases these MUST never include events from federation that do not have https://www.w3.org/ns/activitystreams#Public (also as:Public or Public is valid) set in to. However, they MAY appear within a local representation of a remote actor, if Public set in cc.
The discoverability of an incoming event from federation MUST NOT be controlled by the type of the actor set in attributedTo or by the Activity the event was received (e.g., Announce, Create).
Events SHOULD indicate their organizers by using https://w3id.org/fep/8a8e/organizers. The organizers collection helps provide clarity about the event's management, supports federated discovery, and improves interoperability across platforms.
The value of the organizers field MUST be either an OrderedCollection or a Collection. The items within this collection MAY include actors, or links. If the organizer is not an ActivityPub entity (e.g., a website or organization without an actor), it is recommended to either use a Link or https://schema.org/Person or https://schema.org/Organization.
{\n \"@context\": [\n \"https://schema.org\",\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/foo-bar-party\",\n \"name\": \"New Year's Party\",\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"organizers\": {\n \"type\": \"OrganizersCollection\",\n \"id\": \"https://example.org/foo-bar-party/organizers\",\n \"totalItems\": 3,\n \"first\": {\n \"type\": \"CollectionPage\",\n \"partOf\": \"https://example.org/foo-bar-party/organizers\",\n \"items\": [\n \"https://example.social/users/johnmastodon\",\n \"https://example.org/somewebsite\",\n {\n \"type\": \"Organization\",\n \"id\": \"https://example.events/actors/eventorganizer\",\n \"context\": \"https://example.events/actors/eventorganizer/accept/example.org-foo-bar-party-offer\"\n }\n ]\n }\n }\n}\n"},{"location":"fep/8a8e/#explicit-non-disclosure","title":"Explicit Non-Disclosure","text":"If the event\u2019s organizers are intentionally not disclosed, the organizers field MUST be set to null, have totalItems: 0, or contain an empty items array. This explicitly indicates a deliberate choice to withhold organizer information and takes precedence over any assumptions about organizers based on other fields. In such a case, when receiving an event, clients MUST NOT infer organizers from other event data.
attributedTo","text":"If the organizers property is omitted, it MAY be inferred that the actors in the attributedTo property are the event's organizers. For events with multiple organizers, it is RECOMMENDED to provide an organizers collection to avoid and ensure compatibility with applications that do not support arrays in attributedTo.
Every actor that has published Events SHOULD have a https://w3id.org/fep/8a8e/upcomingEvents collection to list their future or ongoing events. This collection MUST follow these rules:
Collection Type: The collection MUST be an OrderedCollection or one of its subtypes.
Ordering: Events MUST be ordered in ascending order by their startTime, with the earliest events appearing first.
Items: Each item in the collection MUST meet the following criteria:
Event or one of its subtypes. Event's endTime MUST NOT be earlier than the current time, i.e., the event is ongoing or scheduled in the future. Actor is included in the Event's organizers collection.Actor owns the Event, i.e., is part of the Event's attributedTo.Event is in the actor's shares collection, and the Event shares the same host with the actor. This use case is primarily intended to cover relay and group Actors.Note that the terms joinMode and externalParticipationUrl are compatible with and also leaned on the extensions defined by Mobilizon.
https://w3id.org/fep/8a8e/displayEndTime Label Whether to display the endTime Comment A property that defines whether the end time of an event should be displayed. Domain displayEndTime Range Boolean Is defined by FEP-8a8e \n \n {\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"displayEndTime\": false\n }\n \n upcomingEvents URI https://w3id.org/fep/8a8e/upcomingEvents Label Ordered collection of upcoming Events Comment An ActivityStreams OrderedCollection of Event objects that have a startTime property in the future, sorted by startTime with the earliest first. Range An OrderedCollection (@id) Is defined by FEP-8a8e \n \n {\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Organization\",\n \"upcomingEvents\": {\n \"type\": \"OrderedCollection\",\n \"items\": [\n { \"type\": \"Event\", \"startTime\": \"2025-06-01T12:00:00Z\" },\n { \"type\": \"Event\", \"startTime\": \"2025-07-15T15:30:00Z\" }\n ]\n }\n }\n \n OrganizersCollection URI https://w3id.org/fep/8a8e/OrganizersCollection Label An Event's Organizer Collection Comment Inherits all properties from https://www.w3.org/ns/activitystreams#Collection with the addition that the items may also include https://schema.org/Person or https://schema.org/Organization. Subclass of Object See also organizers Is defined by FEP-8a8e \n \n {\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sc\": \"http://schema.org/\"\n }\n ],\n \"type\": \"OrganizersCollection\",\n \"items\": [\n { \"type\": \"Group\", \"name\": \"ActivityPub Group Actor\", \"id\": \"https://example.org/actors/group1\"},\n { \"type\": \"Link\", \"href\": \"https://organizer1.example.org\"},\n { \"type\": \"sc:Person\", \"name\": \"Alice\" },\n { \"type\": \"Organization\", \"name\": \"Event Co.\" }\n ]\n }\n \n organizers URI https://w3id.org/fep/8a8e/organizers Label Organizers Collection Comment An ActivityStreams Collection collection that lists the entities that are disclosed to as organizers of an event. If set to null this explicitly indicates a deliberate choice to withhold organizer information. Range OrganizersCollection (SubType of of the Collection or OrderedCollection ActivityStreams Type) or null Required Yes Functional No Is defined by FEP-8a8e \n \n {\n \"@context\": [\n \"https://schema.org\",\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sc\": \"http://schema.org/\"\n }\n ],\n \"type\": \"Event\",\n \"organizers\": {\n \"type\": \"OrganizersCollection\",\n \"totalItems\": 4,\n \"items\": [\n { \"type\": \"Person\", \"name\": \"ActivityPub Person Actor\", \"id\": \"https://example.org/actors/1\"},\n { \"type\": \"Link\", \"href\": \"https://organizer1.example.org\"},\n { \"type\": \"sc:Person\", \"name\": \"Alice\" },\n { \"type\": \"sc:Organization\", \"name\": \"Event Co.\" }\n ]\n }\n }\n \n AttendeesCollection URI https://w3id.org/fep/8a8e/AttendeesCollection Label A Collection of the Event's attendees Comment Inherits all properties from https://www.w3.org/ns/activitystreams#Collection with the addition that the items may also include https://schema.org/Person or https://schema.org/Organization. The items of the AttendeesCollection are entities that are confirmed attendees by an Events organizer(s). It contains all ActivityPub actors that meet one or more of the following conditions, added as a side effect. \n \n {\n \"@context\": [\n \"https://schema.org\",\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sc\": \"https://schema.org/\"\n }\n ],\n \"type\": \"AttendeesCollection\",\n \"totalItems\": 2,\n \"id\": \"https://example.org/events/alice-birthday-party\"\n \"items\": [\n { \"type\": \"Person\", \"name\": \"Bob\", \"id\": \"https://example.org/actors/bob\"},\n { \"type\": \"sc:Person\", \"name\": \"Alice\", \"email\": \"alice@example.org\"}\n ]\n }\n \n attendees URI https://w3id.org/fep/8a8e/attendees Label Attendees of an Event Comment Is defined by FEP-8a8e Range Collection (ActivityStreams Type) Required No Functional No \n \n {\n \"@context\": [\n \"https://schema.org\",\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sc\": \"https://schema.org#\"\n }\n ],\n \"type\": \"Event\",\n \"attendees\": {\n \"type\": \"OrganizersCollection\",\n \"totalItems\": 4,\n \"items\": [\n { \"type\": \"Person\", \"name\": \"ActivityPub Person\", \"id\": \"https://example.org/actors/1\"},\n { \"type\": \"Link\", \"href\": \"https://organizer1.example.org\"},\n { \"type\": \"sc:Person\", \"name\": \"Alice\" },\n { \"type\": \"sc:Organization\", \"name\": \"Event Co.\" }\n ]\n }\n }\n \n timezone URI https://w3id.org/fep/8a8e/timezone Label The timezone of an Event Comment Indicates the timezone for which the time(s) indicated in the event are given. The value provided should be among those listed in the IANA Time Zone Database. Is defined by FEP-8a8e Range IANA Time Zone identifier Required No Functional No \n \n {\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/events/new-years-party\",\n \"name\": \"New years party\",\n \"startTime\": \"2014-12-31T23:00:00Z\",\n \"endTime\": \"2015-01-01T06:00:00Z\",\n \"timezone\": \"Europe/Vienna\",\n \"organizers\": null\n }\n \n joinMode URI https://w3id.org/fep/8a8e/joinMode rdfs:label Join mode rdfs:comment Indicator of how new members may be able to join an event. Accepted values: free, restricted, external, none, invite. If external, you must also set externalParticipationUrl. rdfs:domain as:Event rdfs:range A string rdfs:isDefinedBy FEP\u20118a8e \n{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"url\": \"http://example.org/events/1234\",\n \"joinMode\": \"restricted\"\n}\n\n \n{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"url\": \"http://example.org/events/1234\",\n \"joinMode\": \"external\",\n \"externalParticipationUrl\": \"https://www.escample.org/events/1234/participate\"\n}\n\n requiredJoinVisibility Label Required Join Visibility Comment Specifies the minimum audience that must be addressed in a valid Join activity related to the event. This can include individual actors, groups, the as:Public collection, or any other URI. The Join activity must be addressed accordingly (e.g., using to, cc, bto, or audience). Domain as:Event Range A list of any addressed targets, i.e. URIs (@list) Is defined by FEP-8a8e \n{\n \"@context\": [\n \"https://schema.org\",\n \"https://https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/yoga-workshop\",\n \"name\": \"Yoga Workshop with Alice and Bob\",\n \"startTime\": \"2014-12-12T18:00:00-08:00\",\n \"endTime\": \"2014-12-12T19:30:00-08:00\",\n \"attributedTo\": \"https://example.org/groups/fediyoga\",\n \"organizers\": {\n \"type\": \"OrganizersCollection\",\n \"id\": \"https://example.org/yoga-workshop/organizers\",\n \"totalItems\": 3,\n \"first\": {\n \"type\": \"CollectionPage\",\n \"partOf\": \"https://example.org/yoga-workshop/organizers\",\n \"items\": [\n \"https://example.org/users/bob\",\n \"https://example.org/users/alice\",\n \"https://example.org/groups/fediyoga\"\n ]\n },\n \"joinMode\": \"restricted\",\n \"requiredJoinVisibility\": [\n \"https://example.org/yoga-workshop/organizers\"\n ]\n}\n externalParticipationUrl URI https://w3id.org/fep/8a8e/externalParticipationUrl rdfs:label External participation URL rdfs:comment A URL that points to an external platform where people can join the event or where they can buy tickets for the event. Required if joinMode is set to external. rdfs:domain as:Event rdfs:range xsd:anyURI rdfs:isDefinedBy FEP\u20118a8e \n{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"url\": \"http://example.org/events/1234\",\n \"joinMode\": \"external\",\n \"externalParticipationUrl\": \"https://www.meetup.com/somegroup/events/00000/\"\n}\n\n isBannerImage URI https://w3id.org/fep/8a8e/isBannerImage rdfs:label Image is a banner image rdfs:comment Whether an image is an (events) banner image. rdfs:domain as:Image rdfs:range Boolean rdfs:isDefinedBy FEP\u20118a8e \n{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\": \"http://joinmastodon.org/ns#\",\n \"focalPoint\": {\n \"@container\": \"@list\",\n \"@id\": \"toot:focalPoint\"\n }\n }\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/new-year-party\",\n \"name\": \"New years party\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"image\": {\n \"type\": \"Image\",\n \"mediaType\": \"image/jpeg\",\n \"url\": \"https://example.com/images/new-year-party-flyer.png\",\n \"focalPoint\": [\n -0.55,\n 0.43\n ]\n },\n \"attachment\": [\n {\n \"type\": \"Image\",\n \"mediaType\": \"image/jpeg\",\n \"url\": \"https://example.com/images/new-year-party-banner.png\",\n \"width\": 1000,\n \"height\": 500,\n \"isBannerImage\": true\n }\n ]\n}\n\n eventStatus URI https://w3id.org/fep/8a8e/eventStatus Label The events status is Comment An eventStatus of an event represents its status; particularly useful when an event is cancelled or rescheduled. Domain Event Range EventStatusType (@vocab) Required No Functional Yes See also EventCancelled | EventScheduled | EventTentative | EventMovedOnline | EventPostponed | EventRescheduled Is defined by FEP-8a8e \n\n{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"id\": \"https://domain.example/events/0\",\n \"type\": \"Event\",\n \"eventStatus\": \"EventScheduled\"\n}\n\n EventStatusType URI https://w3id.org/fep/8a8e/8a8e Label 8a8e Comment EventStatusType is an enumeration type whose instances represent several states that an Event may be in. Subclass of https://schema.org/EventStatusType See also eventStatus Is defined by FEP-8a8e EventScheduled URI https://w3id.org/fep/8a8e/EventScheduled Label The event is scheduled. Comment The event is normally scheduled and planned. Subclass of EventStatusType See also eventStatus Is defined by FEP-8a8e EventCancelled URI https://w3id.org/fep/8a8e/EventPostponed Label The event has been postponed. Comment The event has been postponed and no new date has been set. The event's previousStartTime should be set. Subclass of EventStatusType See also eventStatus Is defined by FEP-8a8e EventRescheduled URI https://w3id.org/fep/8a8e/EventRescheduled Label The event has been rescheduled. Comment The event's previousStartTime should be set to the old date and the startTime should be set to the event's new start date. Subclass of EventStatusType See also eventStatus Is defined by FEP-8a8e EventCancelled URI EventPostponed | EventRescheduled https://w3id.org/fep/8a8e/EventCancelled Label The event has been cancelled. Comment The event has been cancelled. Subclass of EventStatusType See also eventStatus Is defined by FEP-8a8e EventCancelled URI https://w3id.org/fep/8a8e/EventCancelled Label The event has been cancelled. Comment The event has been cancelled. Subclass of EventStatusType See also eventStatus Is defined by FEP-8a8e EventTentative URI https://w3id.org/fep/8a8e/EventTentative Label The event is tentative Comment The event is currently being planned but not confirmed. Subclass of EventStatusType See also eventStatus Is defined by FEP-8a8e EventMovedOnline URI https://w3id.org/fep/8a8e/EventMovedOnline Label The event moved online. Comment Indicates that the event was changed to allow online participation. Subclass of EventStatusType See also eventStatus Is defined by FEP-8a8e previousStartTime URI https://w3id.org/fep/8a8e/previousStartTime Label The event previous start time. Comment The events previous start time is the old start time before an event got postponed or rescheduled. Domain Event Range xsd:dateTime (@vocab) See also EventPostponed | EventRescheduled Is defined by FEP-8a8e category URI https://w3id.org/fep/8a8e/category Label The category of an Event Comment Recommended event categories include: ARTS, AUTO_BOAT_AIR, BOOK_CLUBS, BUSINESS, CAUSES, CLIMATE_ENVIRONMENT, COMMUNITY, COMEDY, CRAFTS, CREATIVE_JAM, DIY_MAKER_SPACES, FAMILY_EDUCATION, FASHION_BEAUTY, FESTIVALS, FILM_MEDIA, FOOD_DRINK, GAMES, INCLUSIVE_SPACES, LANGUAGE_CULTURE, LEARNING, LGBTQ, MEETING, MEDITATION_WELLBEING, MOVEMENTS_POLITICS, MUSIC, NETWORKING, OUTDOORS_ADVENTURE, PARTY, PERFORMING_VISUAL_ARTS, PETS, PHOTOGRAPHY, SCIENCE_TECH, SPIRITUALITY_RELIGION_BELIEFS, SPORTS, THEATRE, WORKSHOPS_SKILL_SHARING Domain Event Range xsd:string (@list) Is defined by FEP-8a8e"},{"location":"fep/8a8e/#credits","title":"Credits","text":"This work would not have been possible without the support of NLnet foundation and NGI Zero.
Thanks are also expressed to the people at OFFDEM who laid out the beginnings of this document.
Special thanks go to Laurin Weger, who was available for many time-consuming discussions. Further thanks go to the Cloudfest Hackathon 2025 for providing space and a platform for further discussion of this FEP and especially to Andreas Heigl for his rich feedback and ideas.
"},{"location":"fep/8a8e/#copyright","title":"Copyright","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/8a8e/AttendeesCollection/","title":"AttendeesCollection","text":"URI : https://w3id.org/fep/8a8e/AttendeesCollection
Label : A Collection of the Event's attendees
Comment : Inherits all properties from https://www.w3.org/ns/activitystreams#Collection with the addition that the items may also include https://schema.org/Person or https://schema.org/Organization. The items of the AttendeesCollection are entities that are confirmed attendees by an Events organizer(s). It contains all ActivityPub actors that meet one or more of the following conditions, added as a side effect.
The items MAY be filtered on privileges of an authenticated user or as appropriate when no authentication is given. This collection *SHOULD* have the totalItems set in any case.
Subclass of : Object
See also : attendees
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/AttendeesCollection/#examples","title":"Examples","text":"Example of an AttendeesCollection with different items
json { \"@context\": [ \"https://schema.org\", \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\", { \"sc\": \"https://schema.org/\" } ], \"type\": \"AttendeesCollection\", \"totalItems\": 2, \"id\": \"https://example.org/events/alice-birthday-party\" \"items\": [ { \"type\": \"Person\", \"name\": \"Bob\", \"id\": \"https://example.org/actors/bob\"}, { \"type\": \"sc:Person\", \"name\": \"Alice\", \"email\": \"alice@example.org\"} ] }
URI : https://w3id.org/fep/8a8e/EventCancelled
Label : The event has been cancelled.
Comment : The event has been cancelled.
Subclass of : EventStatusType
See also : eventStatus
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/EventMovedOnline/","title":"EventMovedOnline","text":"URI : https://w3id.org/fep/8a8e/EventMovedOnline
Label : The event moved online.
Comment : Indicates that the event was changed to allow online participation.
Subclass of : EventStatusType
See also : eventStatus
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/EventPostponed/","title":"EventPostponed","text":"URI : https://w3id.org/fep/8a8e/EventPostponed
Label : The event has been postponed.
Comment : The event has been postponed and no new date has been set. The event's previousStartTime should be set.
Subclass of : EventStatusType
See also : eventStatus
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/EventRescheduled/","title":"EventRescheduled","text":"URI : https://w3id.org/fep/8a8e/EventRescheduled
Label : The event has been rescheduled.
Comment : The event's previousStartTime should be set to the old date and the startTime should be set to the event's new start date.
Subclass of : EventStatusType
See also : eventStatus
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/EventScheduled/","title":"EventScheduled","text":"URI : https://w3id.org/fep/8a8e/EventScheduled
Label : The event is scheduled.
Comment : The event is normally scheduled and planned.
Subclass of : EventStatusType
See also : eventStatus
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/EventStatusType/","title":"EventStatusType","text":"URI : https://w3id.org/fep/8a8e/8a8e
Label : 8a8e
Comment : EventStatusType is an enumeration type whose instances represent several states that an Event may be in.
Subclass of : https://schema.org/EventStatusType
See also : eventStatus
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/EventTentative/","title":"EventTentative","text":"URI : https://w3id.org/fep/8a8e/EventTentative
Label : The event is tentative
Comment : The event is currently being planned but not confirmed.
Subclass of : EventStatusType
See also : eventStatus
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/OrganizersCollection/","title":"OrganizersCollection","text":"URI : https://w3id.org/fep/8a8e/OrganizersCollection
Label : An Event's Organizer Collection
Comment : Inherits all properties from https://www.w3.org/ns/activitystreams#Collection with the addition that the items may also include https://schema.org/Person or https://schema.org/Organization.
Subclass of : Object
See also : organizers
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/OrganizersCollection/#examples","title":"Examples","text":"Example of an OrganizersCollection with different items
json { \"@context\": [ \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\", { \"sc\": \"http://schema.org/\" } ], \"type\": \"OrganizersCollection\", \"items\": [ { \"type\": \"Group\", \"name\": \"ActivityPub Group Actor\", \"id\": \"https://example.org/actors/group1\"}, { \"type\": \"Link\", \"href\": \"https://organizer1.example.org\"}, { \"type\": \"sc:Person\", \"name\": \"Alice\" }, { \"type\": \"Organization\", \"name\": \"Event Co.\" } ] }
URI : https://w3id.org/fep/8a8e/attendees
Label : Attendees of an Event
Comment :
Is defined by : FEP-8a8e
Range : Collection (ActivityStreams Type)
Required : No
Functional : No
"},{"location":"fep/8a8e/attendees/#examples","title":"Examples","text":"Example usage of attendees
json { \"@context\": [ \"https://schema.org\", \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\", { \"sc\": \"https://schema.org#\" } ], \"type\": \"Event\", \"attendees\": { \"type\": \"OrganizersCollection\", \"totalItems\": 4, \"items\": [ { \"type\": \"Person\", \"name\": \"ActivityPub Person\", \"id\": \"https://example.org/actors/1\"}, { \"type\": \"Link\", \"href\": \"https://organizer1.example.org\"}, { \"type\": \"sc:Person\", \"name\": \"Alice\" }, { \"type\": \"sc:Organization\", \"name\": \"Event Co.\" } ] } }
URI : https://w3id.org/fep/8a8e/category
Label : The category of an Event
Comment : Recommended event categories include: ARTS, AUTO_BOAT_AIR, BOOK_CLUBS, BUSINESS, CAUSES, CLIMATE_ENVIRONMENT, COMMUNITY, COMEDY, CRAFTS, CREATIVE_JAM, DIY_MAKER_SPACES, FAMILY_EDUCATION, FASHION_BEAUTY, FESTIVALS, FILM_MEDIA, FOOD_DRINK, GAMES, INCLUSIVE_SPACES, LANGUAGE_CULTURE, LEARNING, LGBTQ, MEETING, MEDITATION_WELLBEING, MOVEMENTS_POLITICS, MUSIC, NETWORKING, OUTDOORS_ADVENTURE, PARTY, PERFORMING_VISUAL_ARTS, PETS, PHOTOGRAPHY, SCIENCE_TECH, SPIRITUALITY_RELIGION_BELIEFS, SPORTS, THEATRE, WORKSHOPS_SKILL_SHARING
Domain : Event
Range : xsd:string (@list)
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/displayEndTime/","title":"displayEndTime","text":"URI : https://w3id.org/fep/8a8e/displayEndTime
Label : Whether to display the endTime
Comment : A property that defines whether the end time of an event should be displayed.
Domain : displayEndTime
Range : Boolean
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/displayEndTime/#examples","title":"Examples","text":"Example usage of displayEndTime
json { \"@context\": [ \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\" ], \"type\": \"Event\", \"displayEndTime\": false }
URI : https://w3id.org/fep/8a8e/eventStatus
Label : The events status is
Comment : An eventStatus of an event represents its status; particularly useful when an event is cancelled or rescheduled.
Domain : Event
Range : EventStatusType (@vocab)
Required : No
Functional : Yes
See also : EventCancelled | EventScheduled | EventTentative | EventMovedOnline | EventPostponed | EventRescheduled
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/eventStatus/#examples","title":"Examples","text":"Example of a forward chronological OrderedCollection with additional context
{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"id\": \"https://domain.example/events/0\",\n \"type\": \"Event\",\n \"eventStatus\": \"EventScheduled\"\n}\n"},{"location":"fep/8a8e/externalParticipationUrl/","title":"externalParticipationUrl","text":"URI : https://w3id.org/fep/8a8e/externalParticipationUrl
rdfs:label : External participation URL
rdfs:comment : A URL that points to an external platform where people can join the event or where they can buy tickets for the event. Required if joinMode is set to external.
rdfs:domain : as:Event
rdfs:range : xsd:anyURI
rdfs:isDefinedBy : FEP\u20118a8e
"},{"location":"fep/8a8e/externalParticipationUrl/#examples","title":"Examples","text":"Example usage of externalParticipationUrl
json{ \"@context\": [ \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\" ], \"type\": \"Event\", \"url\": \"http://example.org/events/1234\", \"joinMode\": \"external\", \"externalParticipationUrl\": \"https://www.meetup.com/somegroup/events/00000/\" }
URI : https://w3id.org/fep/8a8e/isBannerImage
rdfs:label : Image is a banner image
rdfs:comment : Whether an image is an (events) banner image.
rdfs:domain : as:Image
rdfs:range : Boolean
rdfs:isDefinedBy : FEP\u20118a8e
"},{"location":"fep/8a8e/isBannerImage/#examples","title":"Examples","text":"Example usage of isBannerImage
json{ \"@context\": [ \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\", { \"toot\": \"http://joinmastodon.org/ns#\", \"focalPoint\": { \"@container\": \"@list\", \"@id\": \"toot:focalPoint\" } } ], \"type\": \"Event\", \"id\": \"https://example.org/new-year-party\", \"name\": \"New years party\", \"organizers\": null, \"startTime\": \"2014-12-31T23:00:00-08:00\", \"endTime\": \"2015-01-01T04:00:00-08:00\", \"image\": { \"type\": \"Image\", \"mediaType\": \"image/jpeg\", \"url\": \"https://example.com/images/new-year-party-flyer.png\", \"focalPoint\": [ -0.55, 0.43 ] }, \"attachment\": [ { \"type\": \"Image\", \"mediaType\": \"image/jpeg\", \"url\": \"https://example.com/images/new-year-party-banner.png\", \"width\": 1000, \"height\": 500, \"isBannerImage\": true } ] }
URI : https://w3id.org/fep/8a8e/joinMode
rdfs:label : Join mode
rdfs:comment : Indicator of how new members may be able to join an event. Accepted values: free, restricted, external, none, invite. If external, you must also set externalParticipationUrl.
rdfs:domain : as:Event
rdfs:range : A string
rdfs:isDefinedBy : FEP\u20118a8e
"},{"location":"fep/8a8e/joinMode/#examples","title":"Examples","text":"Example: restricted
```json{ \"@context\": [ \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\" ], \"type\": \"Event\", \"url\": \"http://example.org/events/1234\", \"joinMode\": \"restricted\" }
Example: external\n\n```json{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"url\": \"http://example.org/events/1234\",\n \"joinMode\": \"external\",\n \"externalParticipationUrl\": \"https://www.escample.org/events/1234/participate\"\n}\n"},{"location":"fep/8a8e/organizers/","title":"organizers","text":"URI : https://w3id.org/fep/8a8e/organizers
Label : Organizers Collection
Comment : An ActivityStreams Collection collection that lists the entities that are disclosed to as organizers of an event. If set to null this explicitly indicates a deliberate choice to withhold organizer information.
Range : OrganizersCollection (SubType of of the Collection or OrderedCollection ActivityStreams Type) or null
Required : Yes
Functional : No
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/organizers/#examples","title":"Examples","text":"Example usage of organizers
json { \"@context\": [ \"https://schema.org\", \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\", { \"sc\": \"http://schema.org/\" } ], \"type\": \"Event\", \"organizers\": { \"type\": \"OrganizersCollection\", \"totalItems\": 4, \"items\": [ { \"type\": \"Person\", \"name\": \"ActivityPub Person Actor\", \"id\": \"https://example.org/actors/1\"}, { \"type\": \"Link\", \"href\": \"https://organizer1.example.org\"}, { \"type\": \"sc:Person\", \"name\": \"Alice\" }, { \"type\": \"sc:Organization\", \"name\": \"Event Co.\" } ] } }
URI : https://w3id.org/fep/8a8e/previousStartTime
Label : The event previous start time.
Comment : The events previous start time is the old start time before an event got postponed or rescheduled.
Domain : Event
Range : xsd:dateTime (@vocab)
See also : EventPostponed | EventRescheduled
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/requiredJoinVisibility/","title":"requiredJoinVisibility","text":"Label : Required Join Visibility
Comment : Specifies the minimum audience that must be addressed in a valid Join activity related to the event. This can include individual actors, groups, the as:Public collection, or any other URI. The Join activity must be addressed accordingly (e.g., using to, cc, bto, or audience).
Domain : as:Event
Range : A list of any addressed targets, i.e. URIs (@list)
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/requiredJoinVisibility/#examples","title":"Examples","text":"Example usage of requiredJoinVisibility
json{ \"@context\": [ \"https://schema.org\", \"https://https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\" ], \"type\": \"Event\", \"id\": \"https://example.org/yoga-workshop\", \"name\": \"Yoga Workshop with Alice and Bob\", \"startTime\": \"2014-12-12T18:00:00-08:00\", \"endTime\": \"2014-12-12T19:30:00-08:00\", \"attributedTo\": \"https://example.org/groups/fediyoga\", \"organizers\": { \"type\": \"OrganizersCollection\", \"id\": \"https://example.org/yoga-workshop/organizers\", \"totalItems\": 3, \"first\": { \"type\": \"CollectionPage\", \"partOf\": \"https://example.org/yoga-workshop/organizers\", \"items\": [ \"https://example.org/users/bob\", \"https://example.org/users/alice\", \"https://example.org/groups/fediyoga\" ] }, \"joinMode\": \"restricted\", \"requiredJoinVisibility\": [ \"https://example.org/yoga-workshop/organizers\" ] }
URI : https://w3id.org/fep/8a8e/timezone
Label : The timezone of an Event
Comment : Indicates the timezone for which the time(s) indicated in the event are given. The value provided should be among those listed in the IANA Time Zone Database.
Is defined by : FEP-8a8e
Range : IANA Time Zone identifier
Required : No
Functional : No
"},{"location":"fep/8a8e/timezone/#examples","title":"Examples","text":"Example usage of timezone
json { \"@context\": [ \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\" ], \"type\": \"Event\", \"id\": \"https://example.org/events/new-years-party\", \"name\": \"New years party\", \"startTime\": \"2014-12-31T23:00:00Z\", \"endTime\": \"2015-01-01T06:00:00Z\", \"timezone\": \"Europe/Vienna\", \"organizers\": null }
URI : https://w3id.org/fep/8a8e/upcomingEvents
Label : Ordered collection of upcoming Events
Comment : An ActivityStreams OrderedCollection of Event objects that have a startTime property in the future, sorted by startTime with the earliest first.
Range : An OrderedCollection (@id)
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/upcomingEvents/#examples","title":"Examples","text":"Example usage of upcomingEvents
json { \"@context\": [ \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\" ], \"type\": \"Organization\", \"upcomingEvents\": { \"type\": \"OrderedCollection\", \"items\": [ { \"type\": \"Event\", \"startTime\": \"2025-06-01T12:00:00Z\" }, { \"type\": \"Event\", \"startTime\": \"2025-07-15T15:30:00Z\" } ] } }
DRAFT implementation 2022-11-12 #29 Discussions codeberg"},{"location":"fep/8b32/#summary","title":"Summary","text":"This proposal describes how ActivityPub servers and clients could create self-authenticating activities and objects.
HTTP signatures are often used for authentication during server-to-server interactions. However, this ties authentication to activity delivery, and limits the flexibility of the protocol.
Integrity proofs are sets of attributes that represent digital signatures and parameters required to verify them. These proofs can be added to any activity or object, allowing recipients to verify the identity of the actor and integrity of the data. That decouples authentication from the transport, and enables various protocol improvements such as activity relaying, embedded objects and client-side signing.
"},{"location":"fep/8b32/#history","title":"History","text":"Mastodon supports Linked Data signatures since 2017, and a number of other platforms added support for them later. These signatures are similar to integrity proofs, but are based on outdated Linked Data Signatures 1.0 specification, which has been superseded by other standards.
"},{"location":"fep/8b32/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/8b32/#integrity-proofs","title":"Integrity proofs","text":"The proposed authentication mechanism is based on Data Integrity specification.
"},{"location":"fep/8b32/#proof-generation","title":"Proof generation","text":"The proof MUST be created according to the Data Integrity specification, section 4.2 Add Proof.
The process of proof generation consists of the following steps:
The resulting proof is added to the original JSON object under the key proof. Objects SHOULD NOT contain more than one integrity proof.
The list of attributes used in integrity proof is defined in Data Integrity specification, section 2.1 Proofs. The proof type SHOULD be DataIntegrityProof, as specified in section 3.1 DataIntegrityProof. The value of proofPurpose attribute MUST be assertionMethod.
The value of the verificationMethod attribute of the proof can be an HTTP(S) URI or a DID URL.
The controlled identifier document where the verification method is expressed MUST be an actor object or a DID document that is provably associated with an ActivityPub actor (e.g. using a mechanism described in FEP-c390 or FEP-ef61). The verification method MUST be associated with the assertionMethod property of the controlled identifier document. If controlled identifier document is an actor object, implementers SHOULD use assertionMethod property as described in FEP-521a.
Recipients of an object SHOULD perform proof verification if it contains integrity proofs. Verification process MUST follow the Data Integrity specification, section 4.4 Verify Proof. It starts with the removal of the proof value from the JSON object. Then verification method is retrieved from the controlled identifier document as described in Controlled Identifiers specification, section 3.3 Retrieve Verification Method. Then the object is canonicalized, hashed and signature verification is performed according to the parameters specified in the proof.
The subject of the controlled identifier document where the verification method is expressed MUST be the owner of the signed object, or a DID that is provably associated with that actor (e.g. using a mechanism described in FEP-c390 or FEP-ef61).
If both HTTP signature and integrity proof are used, the integrity proof MUST be given precedence over HTTP signature. The HTTP signature MAY be dismissed.
"},{"location":"fep/8b32/#algorithms","title":"Algorithms","text":"Implementers are expected to pursue broad interoperability when choosing algorithms for integrity proofs.
eddsa-jcs-2022 cryptosuite is RECOMMENDED:
Integrity proofs and linked data signatures can be used together, as they rely on different properties (proof and signature, respectively).
If compatiblity with legacy systems is desired, the integrity proof MUST be created and inserted before the generation of the linked data signature.
If both proof and signature are present in a received object, the linked data signature MUST be removed before the verification of the integrity proof.
Implementers using integrity proofs as an authentication mechanism are advised to follow the recommendations given in FEP-fe34: Origin-based security model.
"},{"location":"fep/8b32/#privacy-considerations","title":"Privacy considerations","text":"If a private object is signed, its authenticity can be proven if it is distributed beyond the intended recipients. This risk can be mitigated by encrypting private content.
"},{"location":"fep/8b32/#examples","title":"Examples","text":""},{"location":"fep/8b32/#signed-object","title":"Signed object","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"id\": \"https://server.example/objects/1\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://server.example/users/alice\",\n \"content\": \"Hello world\",\n \"proof\": {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://server.example/users/alice#ed25519-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n}\n"},{"location":"fep/8b32/#signed-activity","title":"Signed activity","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"id\": \"https://server.example/activities/1\",\n \"type\": \"Create\",\n \"actor\": \"https://server.example/users/alice\",\n \"object\": {\n \"id\": \"https://server.example/objects/1\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://server.example/users/alice\",\n \"content\": \"Hello world\"\n },\n \"proof\": {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://server.example/users/alice#ed25519-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n}\n"},{"location":"fep/8b32/#signed-activity-with-embedded-signed-object","title":"Signed activity with embedded signed object","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"id\": \"https://server.example/activities/1\",\n \"type\": \"Create\",\n \"actor\": \"https://server.example/users/alice\",\n \"object\": {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"id\": \"https://server.example/objects/1\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://server.example/users/alice\",\n \"content\": \"Hello world\",\n \"proof\": {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://server.example/users/alice#ed25519-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n },\n \"proof\": {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://server.example/users/alice#ed25519-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n}\n"},{"location":"fep/8b32/#test-vectors","title":"Test vectors","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/8c3f/","title":"FEP-8c3f: Web Monetization","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Diogo Peralta Cordeiro mail@diogo.site, Phablulo Joel phablulo@gmail.comWITHDRAWN 2022-01-18 2023-10-27 #3 - codeberg"},{"location":"fep/8c3f/#summary","title":"Summary","text":"Web Monetization federation via [ActivityPub].
"},{"location":"fep/8c3f/#history","title":"History","text":"The ability to transfer money has been a long-standing omission from the web platform. As a result, the web suffers from a flood of advertising and corrupt business models. Web Monetization provides an open, native, efficient, and automatic way to compensate creators, pay for API calls, and support crucial web infrastructure.
[Web Monetization] is being proposed as a W3C standard at the Web Platform Incubator Community Group.
"},{"location":"fep/8c3f/#requirements","title":"Requirements","text":"In GNU social this is implemented on a plugin using an extra property gs:webmonetizationWallet on the actor object.
{\n \"type\": \"Person\",\n \"streams\": [],\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"gs\": \"https://www.gnu.org/software/social/ns#\"\n },\n {\n \"webmonetizationWallet\": {\n \"@id\": \"gs:webmonetizationWallet\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"id\": \"https://social.gnusocial.rocks/actor/42\",\n \"inbox\": \"https://social.gnusocial.rocks/actor/42/inbox.json\",\n \"outbox\": \"https://social.gnusocial.rocks/actor/42/outbox.json\",\n \"following\": \"https://instance.gnusocial.test/actor/42/subscriptions\",\n \"followers\": \"https://instance.gnusocial.test/actor/42/subscribers\",\n \"preferredUsername\": \"alice\",\n \"name\": \"Alyssa P.Hacker\",\n \"url\": \"https://social.gnusocial.rocks/@alice\",\n \"webmonetizationWallet\": \"$wallet.example.com/alice\"\n}\n"},{"location":"fep/8c3f/#about-the-value-of-gswebmonetizationwallet","title":"About the value of gs:webmonetizationWallet","text":"That string is the same as the example one in Web Monetization specification and it consists on a payment pointer.
Payment Pointers start with a $ character to distinguish them from other identifiers and make it obvious that they are related to payments. To convert a Payment Pointer to a URL the $ is replaced with the standard prefix of a secure URL, https://.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/8fcf/","title":"FEP-8fcf: Followers collection synchronization across servers","text":"Authors Status Date received Date final Tracking issue Discussions Repository Claire claire.fep-1d7d@sitedethib.comFINAL 2020-10-24 2022-02-07 #6 - codeberg"},{"location":"fep/8fcf/#summary","title":"Summary","text":"In ActivityPub, follow relationships are established, updated and removed by sending activities such as Follow, Accept or Reject, which are assumed to be correctly and promptly processed upon receipt.
However, due to incompatible protocol extensions, software bugs, server crashes or database rollbacks, the two ends of a Follow relationship may end up out of sync.
This can be especially damaging when a remote instance has outdated information about follow relationships that should have been revoked, as some implementations may deliver activities addressed to the sender's followers collection by using the sharedInbox mechanism and letting the recipient use the sender's followers collection for local delivery and access control.
This proposal describes an optional mechanism for detecting discrepancies in following relationships across instances, with minimal overhead and without loss of privacy.
"},{"location":"fep/8fcf/#requirements","title":"Requirements","text":"The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].
The proposed protocol for followers collection synchronization makes a number of assumptions that may not be suitable to every implementation or deployment.
Implementations and deployments MUST NOT implement the mechanisms described in this proposal unless they match the following requirements: - actors managed by an instance are required to all share the same exact URI scheme and authority for their id, inbox and sharedInbox URIs - such instances are required to manage all actors using the same URI scheme and authority for either their id, inbox or sharedInbox URIs (that is, for instance, two fediverse implementations cannot implement this proposal if they are set up on the same exact same domain name, unless implementing an additional mechanism to share follower information between them, which is out of scope for this proposal).
The reason for those requirements is to prevent the partial followers collection described below from missing legitimate followers, which could result in followers being removed for no reason.
Failing to implement this proposed synchronization mechanism should not impact compatibility with other implementations, as it is completely optional.
"},{"location":"fep/8fcf/#partial-follower-collection","title":"Partial follower collection","text":"For efficiency and privacy purposes, we consider a subset of an actor's followers collection. This subset is the set of an actor's followers whose id shares an instance's specific URI scheme and authority.
For instance, if https://example.org/users/1 has the following followers: - https://example.org/users/2 - https://testing.example.org/users/1 - https://next.example.org/users/foo - https://testing.example.org/users/2
The partial follower collection of https://example.org/users/1 for the instance serving https://testing.example.org/users/1 is: - https://testing.example.org/users/1 - https://testing.example.org/users/2
To enable quick checking of partial followers consistency across instances, a partial follower collection digest is computed.
This digest is created by XORing together the individual SHA256 digests of each follower's id.
partialCollectionDigest = SHA256(follower1) XOR SHA256(follower2) XOR ... XOR SHA256(followerN)\n For instance, the partial follower collection digest of https://example.org/users/1 for the instance serving https://testing.example.org/users/1 is: 3a06e99569547f444c352ab7f52e4bab207abec5ca6f07b0045cfdc9723f8fa9 XOR f939a1585d4a8f02ee339210dbe7315d7003476663d6095f7d996fc4bc7a49b6 = c33f48cd341ef046a206b8a72ec97af65079f9a3a9b90eef79c5920dce45c61f
Collection-Synchronization HTTP Header","text":"The Collection-Synchronization HTTP header provides a mechanism for quickly checking whether the sender's followers collection part that is relevant to the recipient is consistent with the recipient's knowledge.
The header field name is Collection-Synchronization and its value is a list of parameters and values, formatted according to the signature syntax defined in [HTTP-Signatures], Section 4.1.
Example:
Collection-Synchronization: collectionId=\"https://example.org/users/1/followers\", url=\"https://example.org/users/1/followers_synchronization\", digest=\"c33f48cd341ef046a206b8a72ec97af65079f9a3a9b90eef79c5920dce45c61f\"\n"},{"location":"fep/8fcf/#collection-synchronization-header-parameters","title":"Collection Synchronization Header Parameters","text":"The Collection-Synchronization header's parameters are defined as follows:
collectionId: this is URI of the collection that supports synchronization. It must be the sender's followers collection.url: this is the URL of the partial followers collection intended for the receiving instance. Accessing it should require authentication from the receiving instance.digest: the partial follower collection digest intended for the receiving instance.When delivering an Activity to an inbox (or sharedInbox), an instance MAY set a Collection-Synchronization header intended for the corresponding instance (determined by the inbox URI scheme and authority).
When exactly to set this header is up to the sender, but it is recommended to at least send it for any Create activity addressed specifically to the sender's followers collection.
On the receiving end, upon receiving an Activity delivery with a signed Collection-Synchronization header, the receiver MUST check that: - the collectionId attribute matches the sender's followers collection id - the url attribute also matches the same authority (so that the instance cannot get tricked into requesting the followers list of a third-party individual)
If any of those checks fails, the receiver MUST ignore the Collection-Synchronization header.
The receiver SHOULD then compute the partial collection digest for the sender's followers based on its own knowledge. If the digest does not match the digest attribute of the header, it SHOULD then query the url, authenticating itself to the remote server using [HTTP-Signatures] or another method.
Having fetched the up-to-date partial followers collection from the autoritative server, the receiving end: - SHOULD remove from its local copy of the followers collection any local actor not listed in the partial followers collection. - MAY consider any pending outgoing follow listed in the partial followers collection as accepted. - SHOULD send an Undo Follow for any other local follower listed in the partial followers collection but not known locally.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/9091/","title":"Index","text":"hide: - navigation description: \"This FEP defines an API endpoint used to initiate the \"Export Actor\" operation. The output and semantics of the result of the export operation is out of scope, and left to subsequent FEPs. The endpoint only specifies how to start the operation, and by extension, how to tell if a given Actor's server supports this operation.\"
"},{"location":"fep/9091/#fep-9091-export-actor-service-endpoint","title":"FEP-9091: Export Actor Service Endpoint","text":"Authors Status Date received Tracking issue Discussions Repository Dmitri Zagidulin @dmitri@social.coopDRAFT 2024-07-08 #358 Discussions codeberg"},{"location":"fep/9091/#summary","title":"Summary","text":"This FEP defines an API endpoint used to initiate the \"Export Actor\" operation. The output and semantics of the result of the export operation is out of scope, and left to subsequent FEPs. The endpoint only specifies how to start the operation, and by extension, how to tell if a given Actor's server supports this operation.
"},{"location":"fep/9091/#data-model-export-service-endpoint","title":"Data Model: Export Service Endpoint","text":"Conforming ActivityPub Actor profiles MUST specify their export API endpoints using the service descriptor property (inspired by the DID Core Services section, and used in other FEPs such as FEP-e3e9: Actor Relative URLs):
type of the service descriptor object MUST be https://w3id.org/fep/9091#Export (see example below).service property MUST NOT contain more than one node with the #Export type.#Export, the client SHOULD just use the first entry.{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/did/v1\"\n ],\n \"id\": \"https://alice-personal-site.example/actor\",\n \"type\": \"Person\",\n \"name\": \"Alice\",\n \"service\": [{\n \"id\": \"https://alice-personal-site.example/actor#export\",\n \"type\": \"https://w3id.org/fep/9091#Export\",\n \"serviceEndpoint\": \"https://alice-personal-site.example/actor/accountExport\"\n }]\n}\n"},{"location":"fep/9091/#protocol-initiating-the-export-actor-operation","title":"Protocol: Initiating the Export Actor Operation","text":"If a client conforming to this FEP wishes to start the 'Export Actor' process, such as to create a backup of the Actor profile, its private key material, and all content associated with this actor, the client MUST perform the following steps:
service description object with the expanded type equal to https://w3id.org/fep/9091#Export.Locate the corresponding serviceEndpoint URL, in that description object.
The client makes an HTTP POST to the URL in the serviceEndpoint to initiate the export operation.
Example synchronous request (with an empty body) and response:
POST /actor/accountExport HTTP/1.1\nHost: alice-personal-site.example\nAuthorization: Bearer ....\nAccept: application/x-tar\nContent-Length: 0\n HTTP/1.1 200 OK\nContent-type: application/x-tar\nTransfer-Encoding: chunked\n\n<binary data of the resulting .tar file>\n"},{"location":"fep/9091/#references","title":"References","text":"FEP-e3e9: Actor Relative URLs
Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/9098/","title":"FEP-9098: Custom emojis","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2025-07-06 #648 Discussions codeberg"},{"location":"fep/9098/#summary","title":"Summary","text":"A custom emoji is a small image used to express an idea or emotion. Custom emojis are different from Unicode emojis, which are sequences of characters.
This document describes how custom emojis are implemented in the ActivityPub network.
"},{"location":"fep/9098/#history","title":"History","text":"Custom emojis were introduced by Pleroma (commits) and Mastodon (PR) in 2017.
"},{"location":"fep/9098/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/9098/#emoji-object","title":"Emoji object","text":"Custom emojis are represented as Emoji objects (the full type IRI is http://joinmastodon.org/ns#Emoji). The Emoji object has the following properties:
id (RECOMMENDED): the unique identifier of the custom emoji. Some implementations omit this property (i.e. the object is anonymous).type (REQUIRED): the literal string Emoji.name (REQUIRED): the shortcode of the custom emoji (the name of the emoji, enclosed in colons; see Microsyntax section).updated (OPTIONAL): the date when custom emoji was updated, formatted as RFC-3339 date and time string.icon (REQUIRED): the Image object describing the emoji image.type (REQUIRED): the literal string Image.url (REQUIRED): the URI of the image to use as the emoji.Note
The definition of the icon property in ActivityStreams Vocabulary recommends an aspect ratio of one to one, but in practice custom emojis have varied aspect ratios.
Example:
{\n \"id\": \"https://social.example/emoji/blobcat\",\n \"type\": \"Emoji\",\n \"name\": \":blobcat:\",\n \"updated\": \"1970-01-01T00:00:00Z\",\n \"icon\": {\n \"type\": \"Image\",\n \"url\": \"https://social.example/media/blobcat.png\"\n }\n}\n"},{"location":"fep/9098/#uniqueness","title":"Uniqueness","text":"The primary unique identifier of a custom emoji is a combination of its name and the domain name. The domain name can be extracted from the id of Emoji object or from the id of the object within which it is embedded.
If a publisher doesn't guarantee that emoji id is globally unique, it MUST NOT add this property.
The short description of a custom emoji can be specified with the alternateName property (the full IRI is http://schema.org/alternateName).
In text a custom emoji is represented by its shortcode, which is a name of an emoji enclosed in colons (e.g. :blobcat:). This is also the value of the Emoji object's name property.
These textual representations are often inserted into values of name, summary and content properties of the object that embeds the custom emoji. E.g., the content of a Note, or the name of an Actor.
The corresponding Emoji objects are added to the tag array of the object. Within that array custom emojis can appear in any order.
Example:
{\n \"type\": \"Note\",\n \"id\": \"https://social.example/notes/1234\",\n \"content\": \"<p>:blobcat:</p>\",\n \"tag\": [\n {\n \"id\": \"https://social.example/emoji/blobcat\",\n \"type\": \"Emoji\",\n \"name\": \":blobcat:\",\n \"icon\": {\n \"type\": \"Image\",\n \"url\": \"https://social.example/media/blobcat.png\"\n }\n }\n ]\n}\n"},{"location":"fep/9098/#right-to-left-text","title":"Right-to-left text","text":"Custom emoji shortcodes are always written left-to-right, even in right-to-left text.
"},{"location":"fep/9098/#rendering","title":"Rendering","text":""},{"location":"fep/9098/#general-client-considerations","title":"General client considerations","text":"The aspect ratio of an image SHOULD be preserved. The maximum width of an image MAY be limited.
Shortcodes inside <code> and <pre> HTML elements MUST NOT be replaced.
Web applications typically replace custom emoji shortcodes with <img> HTML tags and render the output as HTML.
To prevent Cross-Site Scripting (XSS) attacks, implementers MUST ensure that:
&<>\"').summary, content) is processed, shortcodes are replaced only inside Text nodes.In order to be compatible with as many servers as possible, implementations SHOULD comply with the following additional requirements:
[a-zA-Z0-9_] set (Mastodon).image/png, image/gif or image/webp (Mastodon).This document is based on implementations of custom emojis in Pleroma, Mastodon, Misskey and Fedibird.
"},{"location":"fep/9098/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/96ff/","title":"Index","text":"hide: - navigation description: \"A number of vulnerabilities have occurred in ActivityPub implementations due to \"type confusion\" attacks - where unrelated files on the same hostnmae as an ActivityPub implementation are processed as obejcts with ActivityPub semantics. Such attacks have been mitigated by carefuly validating the Content-Type header (and by implementations ensuring that users cannot create files with the application/activity+json or application/ld+json content types), but it would bolster such defences if messages intended to be processed with ActivityPub semantics Additionally, ActivityPub nominally supports transfer syntaxes other than JSON-LD (such as any other RDF syntax like Turtle; or potentially a more bandwidth efficient syntax such as a hypothetical CBOR-LD). Strict content type filtering permanently prevents usage of such syntaxes in the future The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \" SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC2119. \"
"},{"location":"fep/96ff/#fep-96ff-explicit-signalling-of-activitypub-semantics","title":"FEP-96ff: Explicit signalling of ActivityPub Semantics","text":"Authors Status Date received Tracking issue Discussions Repository Erin Shepherd fep@erinshepherd.netDRAFT 2024-02-17 #272 - codeberg"},{"location":"fep/96ff/#summary","title":"Summary","text":"A number of vulnerabilities have occurred in ActivityPub implementations due to \"type confusion\" attacks - where unrelated files on the same hostnmae as an ActivityPub implementation are processed as obejcts with ActivityPub semantics.
Such attacks have been mitigated by carefuly validating the Content-Type header (and by implementations ensuring that users cannot create files with the application/activity+json or application/ld+json content types), but it would bolster such defences if messages intended to be processed with ActivityPub semantics
Additionally, ActivityPub nominally supports transfer syntaxes other than JSON-LD (such as any other RDF syntax like Turtle; or potentially a more bandwidth efficient syntax such as a hypothetical CBOR-LD). Strict content type filtering permanently prevents usage of such syntaxes in the future
The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \" SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC2119.
"},{"location":"fep/96ff/#signalling-of-activitypub-semantics","title":"Signalling of ActivityPub Semantics","text":"An implementation signifies its intention to use ActivityPub semantics for a request or response body by including a Link header with relation type type and contents of https://www.w3.org/TR/activitypub/. This header usage is borrowed from LDP, which uses the same relation type to indicate support of LDP semantics.
Link: <https://www.w3.org/TR/activitypub/>;rel=\"type\"\n A conformant implementation MUST include this link relation in any HTTP messages it intends to be processed with ActivityPub semantics.
A conformant implementation MUST process a HTTP message and where this link relation is present and the content type is an implementation supported transport syntax for ActivityStreams 2 with ActivityPub semantics.
A conformant implementation MAY process a HTTP message without any Link header with relation \"type\", where the Content-Type header contains exactly the value application/activity+json or application/ld+json with a profile parameter containing the token https://www.w3.org/ns/activitystreams with ActivityPub semantics (This enables backwards compatibility with existing implementations.)
A conformant implementation MUST NOT process any other HTTP messages with ActivityPub semantics.
Conformant implementations MUST support messages containing multiple Link headers, including multiple link headers with the \"type\" relation. The following examples are all valid and indicate that processing should occur with ActivityPub semantics:
Link: <https://www.w3.org/TR/activitypub/>;rel=\"type\"\n\nLink: <https://www.w3.org/TR/activitypub/>;rel=\"type\", <https://example.com/>;rel=\"test\"\n\nlink: <https://www.w3.org/TR/activitypub/>;rel=\"type\", <https://example.com/>;rel=\"type\"\n\nLINK: <https://www.w3.org/TR/activitypub/>;rel=\"type\"\nLink: <https://example.com/>;rel=\"test\"\n\nLink: <https://www.w3.org/TR/activitypub/>;rel=\"type\"\nlInK: <https://example.com/>;rel=\"type\"\n Implementations are encouraged to periodically add additional Link relations to their messages to ensure support by counterparties. (This process is typically termed greasing, after the GREASE extension to TLS)
For the avoidance of doubt, ActivityPub semantics apply only in cases where implementations expect to transfer ActivityStreams 2 documents with the semantics described in the ActivityPub specification. This link relation MUST NOT be included for other exchanges that an ActivityPub implementation may perform, including but not limited to WebFinger queries.
"},{"location":"fep/96ff/#why-not-just-rely-upon-the-content-type","title":"Why not just rely upon the content type?","text":"The content type alone does not specify semantics. The ActivityStreams 2 syntax can be used independently of ActivityPub, and non-ActivityPub systems such as Cohost produce ActivityStreams 2 documents.
Additionally, even though ActivityPub implementations can prevent the creation of unexpected resources with an ActivityStreams content type, they cannot prevent other applications running on the same name from permitting the untrusted creation of such resources.
It is hoped that a future version of this specification (after widespread deployment of explicit tagging of messages with explicit semantics) can omit the permission to determine semantics based upon media type.
"},{"location":"fep/96ff/#vulnerabilities-non-normative","title":"Vulnerabilities (Non-Normative)","text":"The following is a list of known implementation vulnerabilities which would have been prevented by implementation of this mechanism:
If all implmenetations strictly required implementation of the link relation check, the following vulnerabilities would also be prevented:
(This is not presently proposed because it would break compatibility with existing implementations)
These lists are likely not exhaustive.
"},{"location":"fep/96ff/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/9967/","title":"FEP-9967: Polls","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2025-01-23 #478 Discussions codeberg"},{"location":"fep/9967/#summary","title":"Summary","text":"How to make polls in ActivityPub network.
"},{"location":"fep/9967/#history","title":"History","text":"Mastodon implemented polls in 2019 (documentation).
ActivityStreams specification provides recommendations for representing questions, but these recommendations were found to be impractical and the de-facto standard differs from them.
"},{"location":"fep/9967/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/9967/#overview","title":"Overview","text":"sequenceDiagram\n actor Alice\n actor Bob\n actor Charlie\n Note right of Alice: Alice publishes a poll\n Alice ->> Bob: Create(Question)\n Alice ->> Charlie: Create(Question)\n Note left of Bob: Bob votes\n Bob ->> Alice: Create(Note)\n Note right of Alice: Alice receives the vote and updates poll results\n Alice ->> Bob: Update(Question)\n Alice ->> Charlie: Update(Question)"},{"location":"fep/9967/#question-object","title":"Question object","text":"A poll is represented by a Question object. It MUST be published with Create activity (in contrast with ActivityStreams vocabulary where Question itself is defined as intransitive activity).
A Question object is typically structured similarly to a Note. In addition to common properties such as attributedTo and content, it MUST have one of the following:
oneOf, if the poll requires choosing a single option.anyOf, if the poll allows choosing multiple options.The value of this property MUST be an array of objects representing poll options:
type: the Note string.name: the text of the poll option.replies: the total number of votes, as a collection:type: the Collection string.totalItems: the total number of votes for this poll option.The name of a poll option MUST be unique within a poll. If the text contains custom emoji microsyntax, the corresponding Emoji objects are added to the tag array of the Question object.
The number of poll options is often limited, but the limit differs between implementations.
Question objects MAY have an endTime property indicating the time when the poll ends. Some implementations use closed property instead of endTime or in addition to it (even if the poll is open). Consumers MUST process closed in the same way as endTime.
Question objects SHOULD have an updated property.
Example:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://social.example/polls/1\",\n \"type\": \"Question\",\n \"attributedTo\": \"https://social.example/actors/1\",\n \"to\": \"https://www.w3.org/ns/activitystreams#Public\",\n \"content\": \"<p>Question</p>\",\n \"endTime\": \"2024-07-17T18:18:17Z\",\n \"updated\": \"2024-07-16T20:53:05Z\",\n \"oneOf\": [\n {\n \"type\": \"Note\",\n \"name\": \"Answer 1\",\n \"replies\": {\n \"type\": \"Collection\",\n \"totalItems\": 596\n }\n },\n {\n \"type\": \"Note\",\n \"name\": \"Answer 2\",\n \"replies\": {\n \"type\": \"Collection\",\n \"totalItems\": 379\n }\n }\n ]\n}\n"},{"location":"fep/9967/#voting","title":"Voting","text":""},{"location":"fep/9967/#vote-object","title":"Vote object","text":"A vote is represented by a Note object with the following structure:
id: the ID of the vote.type: the Note string.attributedTo: the actor that is making this vote.inReplyTo: the ID of the Question object.name: the value of the name property of the chosen poll option.to: the author of the poll.The object MUST NOT have a content property.
This object is wrapped in a Create activity and sent to the author of the poll. If the poll is anonymous, this activity MUST NOT be delivered to anyone else.
When a poll allows multiple choices, each vote MUST be sent as a separate activity.
Example of a vote:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://social.example/votes/1\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://social.example/actors/2\",\n \"inReplyTo\": \"https://social.example/polls/1\",\n \"name\": \"Answer 1\",\n \"to\": \"https://social.example/actors/1\"\n}\n"},{"location":"fep/9967/#receiving-a-vote","title":"Receiving a vote","text":"The structure of a vote is very similar to a direct reply. It can be identified as a Note with name and inReplyTo properties, but without a content property.
Upon receving a vote, the author of the poll performs the following checks:
endTime is specified).id is not already registered.name is not already registered (if multiple choices are not allowed).If any of these checks fails, the vote MUST be ignored.
Otherwise, the author of the poll updates the vote count for the specified poll option (the totalItems property of a corresponding replies collection). The value of the updated property is changed to the current date and time.
When actor that published a poll receives a vote, it publishes an Update activity containing the Question object with updated results. This activity MUST be delivered to the audience of the poll and to every actor that voted in the poll.
The type of a poll (single choice / multiple choices) and its options might be changed at any time. In that case the author of the poll MUST reset the vote counts.
"},{"location":"fep/9967/#future-work","title":"Future work","text":"The method of identifying votes described in this document is not reliable because a reply can look exactly like a vote. Alternative representations are being discussed (e.g. the Respond activity).
This document is based on implementations of polls in Mastodon and Pleroma.
"},{"location":"fep/9967/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/9fde/","title":"FEP-9fde: Mechanism for servers to expose supported operations","text":"Authors Status Date received Tracking issue Discussions Repository Nik Clayton nik@ngo.org.ukDRAFT 2024-03-14 #282 - codeberg Version Date Changes 3.1 2024-03-13 - Convert to FEP format- Incorporate feedback from Johannes Ernst and Steve Bates- Renamed clientApis to operations so this is usable for server-server operations too- Added \"Open questions\" section 3.0 2024-01-22 Re-write; include the operation information in the nodeinfo 2.0 2023-09-25 Re-write; replace the original suggestion to use the OpenAPI definition with a simpler specification 1.0 2023-08-14 Initial draft"},{"location":"fep/9fde/#summary","title":"Summary","text":"This document proposes an extension to the NodeInfo schema (FEP-f1d5) that would allow developers of Mastodon and Mastodon-like servers to unambigiously communicate the operations their servers support, and allow developers of software that interoperates with those servers to detect those features, promoting interoperability and easier adoption of new features.
"},{"location":"fep/9fde/#synopsis","title":"Synopsis","text":"This document is written for:
After reading this document you should:
The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in RFC-2119.
"},{"location":"fep/9fde/#overview","title":"Overview","text":"[!IMPORTANT] Other servers are also clients
The primary perspective of this document's author is as an end-user client developer dealing with the Mastodon client API, and the examples are written from that perspective. However, this proposal can also be used to describe operations between different servers in the Fediverse using other protocols (e.g., ActivityPub). For any given interaction between a pair of servers one of them is going to be acting as the client in the interaction, and will need to know the operations the server supports.
For the purposes of this document a \"Mastodon or Mastodon-like\" server is a server that presents the Mastodon client API, optionally with extensions to that API that provide additional functionality. These servers include, but are not limited to:
Clients of these servers have an API discovery problem. Since different servers support different (but similar) APIs the client has to determine what API operations the server supports.
Given the wide variety of servers that are available, and their many forks, it's not feasible for clients to maintain an accurate list of all the possible server software names while mapping the names to API features.
Instead the server should have a mechanism for advertising the operations it supports.
The client would use this when determining what features to show the user, without needing to employ complex, error-prone heuristics.
This would also provide a clear mechanism for Mastodon and Mastodon-like servers to incrementally deploy new features and deprecate old ones without inconveniencing clients.
It also provides a clear mechanism to advertise server functionality without continually bolting it on to the \"instance info\" mechanism in the inconsistent fashion that has been done so far.
The rest of this document sets out the specific problems I'm interested in solving, with motivating examples, and then describes how the new approach would solve these problems.
"},{"location":"fep/9fde/#problems","title":"Problems","text":""},{"location":"fep/9fde/#the-supported-api-is-not-easily-discoverable","title":"The supported API is not easily discoverable","text":"Changes are made to the Mastodon API in a manner that is not easily discoverable by clients.
For example, Add POST /api/v1/conversations/:id/unread by ClearlyClaire \u00b7 Pull Request #25509 \u00b7 mastodon/mastodon \u00b7 GitHub adds a new API endpoint (api/v1/conversations/:id/unread).
The only way a client can discover that this API exists is to maintain, per-client, a mapping between Mastodon server version and the API supported at each version.
This is:
The Instance information contains a configuration block that has some, but not all the information necessary to determine the features a server supports.
Other servers have extended this information in incompatible ways (e.g., the pleroma block).
Other servers may not implement the functionality at all. For example, GoToSocial did not implement support for the Mastodon client-side filter API until version 0.15.0.
"},{"location":"fep/9fde/#no-standard-way-for-mastodon-like-servers-to-advertise-their-additional-functionality-to-clients","title":"No standard way for Mastodon-like servers to advertise their additional functionality to clients","text":"Mastodon-like servers implement some or all of the Mastodon API.
In many cases they also extend the API, providing additional functionality (local-only posting, quoting, markdown formatting, bookmarks, etc.)
In some cases that functionality has already been incorporated in Mastodon (e.g., bookmarks), in other cases there are plans to include that functionality in Mastodon (e.g., quoting, markdown formatting).
This leads to three problems.
Server developers already have a lot of work to do. Any proposal should therefore be straightforward to implement. Additional complexity, such as changing the contents of existing API responses, or requiring developers of different servers to tightly coordinate when new functionality is introduced is going to make it less likely that groups adopt any proposed solutions.
"},{"location":"fep/9fde/#proposed-solution","title":"Proposed solution","text":"A given Mastodon or Mastodon-like server supports a set of operations.
To expose those to the user a Mastodon client needs to know:
Therefore we need:
Operations are identified by the reverse FQDN of the server software that first implemented that operation, then an arbitrary number of additional dot-separated labels determined by the server authors.
This ensures that operation IDs are unique without needing tight coordination between different server developer groups.
For example:
org.joinmastodon.api.statuses.postorg.joinmastodon.api.statuses.translateio.github.glitch-soc.api.statuses.bookmarkdev.iceshrimp.api.notes.reactions.create[!NOTE] Precise reverse FQDN to use for each server is to be decided
This example use the reverse FQDNs for the server's primary websites or documentation sites, but each server group would determine and document the reverse FQDN for their server's operations.
[!NOTE] Dot-separated labels SHOULD map 1:1 to API endpoint components
In these examples the dotted components after the api correspond to the path components of the API endpoint. While there is no requirement that they do so server implementors are RECOMMENDED to do so, as it makes it easier for developers to mentally map between the operation identifier and the API path when they are working with the code.
Each label within the operation identifier is represented in lower-case US-ASCII (a character set containing 128 characters associated with code points in the range 0..7F) . If a server team wishes to define an operation that uses a non-US-ASCII label they MUST encode the operation label to US-ASCII using the ToASCII transformation described in RFC 3490 - Internationalizing Domain Names in Applications (IDNA).
Each operation exists at one or more semver-compatible (v2.0.0) versions. Semver is used because it is a widely deployed standard, easily understandable, and client libraries that can parse this format are available across many different programming languages.
For example, in the Mastodon API documentation \"Post a new status\" describes the API for posting a new status. That API has changed three times in the Mastodon server implementation.
scheduled_atpollThere are no backwards-incompatible breaking changes across those versions so this is the same operation at three different versions; per Semver the major version stays the same and the minor version is incremented.
1.0.0 - initial implementation1.1.0 - support for scheduled_at1.2.0 - support for polls[!IMPORTANT] These version numbers are unrelated to the version number of the software that introduced the operation
"},{"location":"fep/9fde/#example-bookmarks","title":"Example: Bookmarks","text":"Bookmarking statuses originated in the glitch-soc fork and was incorporated in to Mastodon.
Therefore, the ID for the bookmark operations -- if they are compatible with the glitch-soc implementation -- use the io.github.glitch-soc.api prefix.
io.github.glitch-soc.api.statuses.bookmark @ 1.0.0 - bookmark a statusio.github.glitch-soc.api.statuses.unbookmark @ 1.0.0 - remove a status from bookmarksio.github.glitch-soc.api.timeline.bookmarks @ 1.0.0 - fetch a timeline of the user's bookmarksio.github.glitch-soc.api.timeline.bookmarks @ 1.1.0 - fetch a timeline of the user's bookmarks, supporting min_id and max_id simultaneouslyClients must be able to discover which operations the server supports and the endpoints to use for those operations.
To do this the nodeinfo (determined via /.well-known/nodeinfo) schema should be extended to support a new operations property.
The property's value is a map from a string key -- the operation ID -- to a set of one or more Semver versions of the operation that the server supports.
For example:
\"operations\": {\n ...\n \"org.joinmastodon.api.some.operation\": [\"1.0.0\", \"1.1.0\", \"1.2.0\", \"2.0.0\"]\n ...\n}\n [!NOTE] Unordered versions
The supported version operations is not ordered; client code MUST treat this as a set, not a list.
[!NOTE] Not limited to Mastodon / Mastodon-like servers
This operations map is not limited to operations supported by Mastodon/Mastodon-like servers. This is a general mechanism that can be used by servers to expose information about their supported operations and could be used by other Fediverse software like Lemmy, KBin, etc.
Because of the Semver rules for breaking changes servers MAY omit earlier versions from the list if they are included in a later version. In the previous example the 1.0.0 and 1.1.0 versions can be omitted as a server supporting v1.2.0 of an operation implicitly supports all preceding versions with the same major number.
\"operations\": {\n ...\n \"org.joinmastodon.api.some.operation\": [\"1.2.0\", \"2.0.0\"]\n ...\n}\n [!NOTE] There is no need to specify the operation semantics
The semantics of each {operation, version} pair are already known by the client (for each operation it supports). Semantics like whether these endpoints are GET, POST, DELETE, or PATCH, the exact names of the URL query parameters, the API endpoint, etc.
In other words, servers MUST NOT advertise support for an existing operation ID and change anything about how that operation works. The server developers should either define and document a new operation ID, or implement the operation as a new version (bumping the major version if it is a breaking change).
"},{"location":"fep/9fde/#activitypub-extension-support","title":"ActivityPub extension support","text":"Most of this document has presented examples using the Mastodon client API (and related APIs) but, as noted, this proposal also provides a clean mechanism to allow servers to report other extensions they support.
Here is an example from FEP 6481 - # Specifying ActivityPub extension support with NodeInfo which is presented as:
Manyfold intends to define a new Activity Vocabulary Object type for its own rich content, 3dModel. The specification will define an extension IRI, for instance https://w3id.org/manyfold/3dModel#v1.
Applications that wish to receive Activities with this type can state their support for the extension in their NodeInfo file as follows; Manyfold instances will then know that they can send that object type and it will be understood.
{\n ...,\n \"metadata\": {\n \"activitypub\": {\n \"extensions\": [\n \"https://w3id.org/manyfold/3dModel#v1\"\n ]\n }\n }\n}\n In this proposal that could represented like this:
\"operations\": {\n \"app.manyfold.activitypub.accept.3dmodel\": [\"1.0.0\"],\n ...\n}\n (the precise name of the operation would need to be determined)
Any other server that can accept a 3dModel would expose the same operation.
Servers where the set of supported operations is not user configurable would need to maintain a static map of operations to versions, and return that map as part of the nodeinfo response.
If the set of operations is user configurable (e.g., perhaps the server software supports a translation API but the server operator has not enabled translation support) the nodeinfo response would need to be dynamically generated from the current software configuration.
In both cases developing a new operation or changing an existing operation would require the developers to:
To provide the best user experience client developers SHOULD fetch the operations map when the user logs in.
If the client supports a particular operation at a particular version the client can query the map and determine whether the concrete version they need is in the map, or met by a higher version. Semver client libraries are available for Kotlin and Java (Android) and Swift (iOS), as well as many other languages.
If the server does not support the operation the client MAY fall back to a different operation, or disable the particular operation in the UI.
To use the example from earlier, Add POST /api/v1/conversations/:id/unread by ClearlyClaire \u00b7 Pull Request #25509 \u00b7 mastodon/mastodon \u00b7 GitHub which adds a new API endpoint (api/v1/conversations/:id/unread).
The server would report this as:
\"operations\": {\n ...\n \"org.joinmastodon.api.conversations.id.unread\": [\"1.0.0\"]\n ...\n}\n and a client that wanted to conditionally support this would query the operations map for org.joinmastodon.api.conversations.id.unread with any version entry with a major version of 1, and if the operation/version pair is not found then disable the \"Mark a conversation unread\" UI affordances where they occur.
Yes.
I have started implementing the client side of this in Pachli. At the moment this uses server version parsing heuristics to maintain a Pachli-specific map of operations and supported versions (Server.kt ) and then query the server's reported capabilities and adjust the UI accordingly.
For example, this snippet conditionally enables the \"edit filters\" UI only if the user's server supports filtering.
Maintaining the server-specific operations map in Pachli is error prone, slow to update, and does not benefit the wider ecosystem of Mastodon clients and servers, hence this proposal.
"},{"location":"fep/9fde/#this-solves","title":"This solves...","text":"This solves the problems described earlier:
operations property must reflect the active configuration of the server.org.mastodon... operation identifier.This proposal doesn't address how clients can discover any limits associated with the operations. For example, how many characters are allowed per post, or the number of options that can be included in a poll.
That information is already included in the server's /api/v2/instance call (in the language of this proposal, the org.mastodon.api.instance operation).
I did consider extending the operations definition so that each operation mapped to an object that contained multiple keys, like this:
\"operations\": {\n \"org.joinmastodon.api.statuses.post\": {\n \"1.0.0\": {\n \"endpoint\": \"/api/v1/statuses\",\n \"limits\": {\n \"max_characters\": 500,\n // ...\n },\n \"mimeTypes\": [\"text/plain\"],\n // ...\n },\n \"1.1.0\": { /* ... */ }\n }\n}\n That would significantly complicate this proposal, increasing the risk that it's not adopted. There's also no clear value in doing this.
"},{"location":"fep/9fde/#alternatives-considered","title":"Alternatives considered","text":""},{"location":"fep/9fde/#reporting-capabilities-alongside-operations","title":"Reporting capabilities alongside operations","text":"It's tempting to think that operations could be broken down in to smaller parts.
For example, instead of different versions for the \"post a status\" operation you could include more specific capabilities in the operation description:
\"operations\": {\n ...\n \"org.joinmastodon.api.statuses.post\": {\n \"contentWarning\": true,\n \"polls\": true,\n \"media\": true,\n ...\n }\n ...\n}\n This indicates this server supports the \"post a new status\" operation with statuses that include content warnings, polls, and media.
You don't do that because it results in a combinatorial explosion of the different sub-types of operations that clients need to support, without any significant benefit.
Even the example above is incomplete; for example, some servers support including images in content warnings, so a simple boolean for the contentWarning property is insufficient.
So treating the thing-that-has-to-be-versioned as the operation (post a status, translate, reblog, etc) seems to be the better level of granularity.
"},{"location":"fep/9fde/#reporting-capabilities-in-api-responses","title":"Reporting capabilities in API responses","text":"A server could include metadata in each response that contains an object that describes the operations that can be performed on that object. For example, the Status object could be modified to include an operations property that looks like this:
{\n \"id\": \"103270115826048975\",\n \"created_at\": \"2019-12-08T03:48:33.901Z\",\n ...\n \"operations\": {\n \"org.joinmastodon.api.statuses.reply\": [\"POST\", \"https://example.com/api/v1/statuses\"],\n \"org.joinmastodon.api.statuses.view\": [\"GET\", \"https://example.com/api/v1/statuses/103270115826048975\"],\n \"org.joinmastodon.api.statuses.favourite\": [\"POST\", \"http/api/v1/statuses/103270115826048975/favourite\"],\n ... etc\n }\n }\n}\n This is the Hypermedia as the engine of application state (HATEOAS) model.
It's an interesting approach, and a possible future direction. But it would require significant work on the part of server developers to implement as it would affect every response returned by the server.
On the other hand the approach in this proposal is static content in the nodeinfo response. It's significantly easier to implement and iterate on.
"},{"location":"fep/9fde/#clients-keep-a-hardcoded-server-version-capabilities-map","title":"Clients keep a hardcoded server version : capabilities map","text":"This could go the other way, and instead require servers to have a consistent name and parseable version number, and expect clients to keep a map of \"server A at version V can perform operations X, Y, and Z\".
I think this is the wrong approach for two reasons:
First:
Re that last point a worked example might make it clearer.
Suppose there are two server types, A and B. A supports operations X and Y, B supports X, Y, and Z.
A client is released which supports operations X, Y, and Z, and is hardcoded with knowledge about which server type supports a given operation.
A new version of server type A is released which now supports operation Z as well. But users of the client who connect to server type A cannot benefit from this until a new version of the client is released with updated information about the capabilities of server type A.
With the proposal in this document this problem does not occur; if a client supports operation Z (at a given version) and a server advertises that it supports that operation then the client can choose to use it without needing a new release.
This is better for our users.
Second:
Server developers do not seem to be interested in reporting useful versions for their software, for example:
OpenAPI is a popular schema for defining an API. The server could just return the OpenAPI schema for the API that it supports.
I did consider this (an earlier version of this proposal was built around it). But it complicates the data the client needs to process, and includes data that the client will ignore.
Consider the /api/v1/timelines/home endpoint, which would have an operation ID something like org.joinmastodon.api.timelines.home under this proposal.
This is the OpenAPI definition for that endpoint, copied from the GoToSocial project's OpenAPI definition (swagger.yaml, the descriptions have been deleted to keep this a reasonable length):
/api/v1/timelines/home:\n get:\n description: |-\n The statuses [... deleted ...]\n operationId: homeTimeline\n parameters:\n - description: [deleted]\n in: query\n name: max_id\n type: string\n - description: [deleted]\n in: query\n name: since_id\n type: string\n - description: [deleted]\n in: query\n name: min_id\n type: string\n - default: 20\n description: [deleted]\n in: query\n name: limit\n type: integer\n produces:\n - application/json\n responses:\n \"200\":\n description: Array of statuses.\n headers:\n Link:\n description: [deleted]\n type: string\n schema:\n items:\n $ref: '#/definitions/status'\n type: array\n \"400\":\n description: bad request\n \"401\":\n description: unauthorized\n security:\n - OAuth2 Bearer:\n - read:statuses\n summary: See statuses/posts by accounts you follow.\n tags:\n - timelines\n Most of the information in that definition is redundant for the client.
It's absolutely essential information to have for the server developer, and for producing documentation.
But the client should already have this compiled in. The contract between the client and the server is that if the server reports that it supports the org.joinmastodon.api.timelines.home operation at v1.0.0:
/api/v1/timelines/homemax_id, since_id, min_id, and limit.StatusLink headerSo returning an OpenAPI definition to the client significantly complicates things for no benefit.
OpenAPI is also endpoint-oriented; by which I mean that the definition leads with the endpoint (/api/v1/statuses) and then describes the single operation that is present at that endpoint.
This is backwards to what we need, where the operation comes first, and multiple operations might be supported at the same endpoint.
"},{"location":"fep/9fde/#use-the-api-path-as-the-key","title":"Use the API path as the key","text":"Instead of the proposed format, use the API path as the map key, like this:
\"operations\": {\n \"/api/some/path\": [\"org.joinmastodon.x.y.z:1.0.0\", \"org.joinmastodon.x.y.z:1.1.0\", ...]\n}\n I reject this for two reasons:
These are questions this proposal does not explicitly answer. I have suggestions, but feedback is appreciated.
"},{"location":"fep/9fde/#can-servers-set-an-api-prefix","title":"Can servers set an API prefix?","text":"While I do not believe servers should be able to arbitrarily change all aspects of the path an operation is served under it may be useful for servers to be able to specify a path prefix for some or all operations.
E.g., a server that supports Mastodon and Friendica APIs might want to offer them at paths that starts mastodon and friendica respectively.
To do this the map value would be a second object with prefix and versions keys.
\"operations\": {\n \"org.joinmastodon.api.conversations.id.unread\": {\n \"prefix\": \"/mastodon\",\n \"versions\": [\"1.0.0\"]\n },\n \"ca.friendi.api.conversations.id.unread\": {\n \"prefix\": \"/friendica\",\n \"versions\": [\"1.0.0\"]\n }\n}\n If the prefix was omitted the default would be /.
Adopting this format, even if the only key is versions is probably a good idea initially to support future changes in a backwards-compatible manner.
If a FQDN owner entity rebrands or disappears (e.g., Calckey renamed to Firefish) what happens to the operation identifiers they have defined?
I don't think a rebrand should necessitate a wholesale renaming of the operation identifiers, that's unnecessary churn. They may decide to define future operations using the new name.
Since the operation identifies are not actual domains / URLs there is no requirement for them to actually resolve to anything, so I don't foresee a technical problem with this approach if a project that defined some operation identifiers shuts down. As long as there is still documentation that defines the API for those operations nothing is lost.
"},{"location":"fep/9fde/#is-the-reverse-fqdn-with-suffix-approach-the-best-format-for-operations-keys","title":"Is the reverse-FQDN-with-suffix approach the best format for operations keys?","text":"I think so.
I did consider using Uniform Resource Name (URN) or Internationalised Resource Identifiers (IRI) . There are advantages; for example, the operation identifier could be a URN or IRI that resolves to a document (perhaps the OpenAPI definition) for the API for that operation.
However, that introduces the difficult topic of case-sensitivity when clients are comparing the list of operations the server returns with the list of operations they support.
If the operation key was a URN or IRI it is, by definition, case-sensitive in parts. This provides an opportunity for server developers to incorrectly report an identifier with the wrong case, and for client developers to look for operations with the wrong case, impeding interoperability.
Suggesting \"Clients should case-fold operation identifiers to lower case before comparing them\" does not solve the problem, as rules for correctly lower-casing text are not well specified, and may differ from implementation to implementation, again impeding interoperability.
The approach of using a reverse FQDN with additional labels, and ensuring it is encoded with IDN sidesteps those problems.
"},{"location":"fep/9fde/#is-there-a-11-mapping-between-operations-and-api-calls","title":"Is there a 1:1 mapping between operations and API calls?","text":"Should every supported API call have an associated operation, or is it OK to consider multiple API calls as a single operation the server supports?
I think a 1:1 mapping makes most sense, as it allows server developers to incrementally deploy new features.
For example, at the time of writing Friendica only partially supports voting; posts with votes can be displayed and will be returned by the API, but the operations to create a post with a poll, or to vote on a poll are not implemented.
With a 1:1 mapping beween API and operations a Friendica server can advertise \"I can return posts that can contain polls; I do not support API calls that post polls or vote on them\".
If \"voting\" was treated as a single operation then Friendica would be forced to advertise \"I do not support polls\", and a client might unnecessarily not show polls attached to Friendica posts.
Anything other than a 1:1 mapping could also lead to different server developer teams \"bundling\" API calls into single operations in different ways, making things more complex for client developers, not less.
"},{"location":"fep/9fde/#should-clients-indicate-the-operation-identifier-when-making-a-request","title":"Should clients indicate the operation identifier when making a request?","text":"When a server supports multiple versions of an API at a single endpoint it can be challenging to reliably determine the version of the API the client is calling. Server software often has to resort to \"sniffing\" the request and deducing the client's intentions by the presence or absence of specific properties in the request.
This makes it more difficult to write the code to process the request and to determine what error details should be returned to the client.
I think trying to solve this problem is outside the scope of this proposal. However, if this proposal is adopted I recommend server developers include the operation ID as a mandatory specific property in all future changes to their API, so this problem is gradually solved.
"},{"location":"fep/9fde/#does-this-need-to-be-a-new-top-level-key-in-the-nodeinfo","title":"Does this need to be a new top-level key in the nodeinfo?","text":"No. This could also be in the metadata, exposed as:
{\n \"version\": \"2.2\",\n ...\n \"metadata\": {\n \"operations\": {\n // operation data here\n }\n }\n}\n"},{"location":"fep/9fde/#related-links-prior-art","title":"Related links / prior art","text":"Not an exhaustive list:
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/a070/","title":"Index","text":"hide: - navigation description: \"In a [Github-issue] filed against the normative AS2 context, it was pointed out that attachment and tag are unordered by default, although some implementations of \"fediverse\" software blindly assume them to always be ordered. This can be made unambiguous by using @list in JSON-LD, but for plain JSON consumers, a separate shorthand term must be defined. This FEP attempts to disambiguate between unordered and ordered arrays for those plain JSON consumers.\"
"},{"location":"fep/a070/#fep-a070-ordered-properties-for-plain-json-consumers","title":"FEP-a070: Ordered properties for plain JSON consumers","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT 2023-06-13 #121 - codeberg"},{"location":"fep/a070/#summary","title":"Summary","text":"In a [Github-issue] filed against the normative AS2 context, it was pointed out that attachment and tag are unordered by default, although some implementations of \"fediverse\" software blindly assume them to always be ordered. This can be made unambiguous by using @list in JSON-LD, but for plain JSON consumers, a separate shorthand term must be defined. This FEP attempts to disambiguate between unordered and ordered arrays for those plain JSON consumers.
items vs orderedItems","text":"(This section is non-normative.)
In the [AS2-vocab], there is items, which is used to express the items included in a Collection or OrderedCollection. However, in [ActivityPub], the OrderedCollection type is mandated to be reverse chronological order. Tangentially, it is valid to have a Collection with items that are either ordered or unordered.
The normative [AS2-context] deals with issue by defining two different terms: items and orderedItems. Both of these terms have the same @id of https://www.w3.org/ns/activitystreams#items and the same @type of @id to indicate that they contain nodes on the graph, but orderedItems is additionally defined with a @container of an ordered @list, overriding the default @container of an unordered @set.
The use of orderedItems is present in the examples for items within [AS2-vocab], but it is not defined separately because it is not a separate term. Following this pattern, we can define similarly \"ordered\" counterparts to \"unordered\" existing properties.
(This section is non-normative)
attachment : There is nothing that requires attachments to an object to be ordered; however, it is a popular expectation in several cases that the attachments should be ordered. For example, someone authoring an object may wish to attach three images in order, representing a triptych. If the array order of attachment were to be changed, this would change the author's intended representation.
tag : Tags are generally freeform and not meant to be parsed in any order, but some use-cases may wish to order tags as well. For example, Tumblr allows users to set the order their tags are presented in, and users sometimes use these tags to communicate a series of comments or thoughts without having them be present in the conversational context.
name : A thing may have multiple names, some of which are preferred more than others. Having an array for name is likely to confuse existing implementations at the time of writing this FEP, but it is something that could conceivably be useful for several use-cases.
oneOf/anyOf : When a Question represents a poll with predefined options for possible answers, the order of the options may be relevant to understanding the poll. Consider a self-referential poll that asks you to predict which option will receive the most votes: \"option 2\", \"option 3\", or \"option 1\".
"},{"location":"fep/a070/#terms-defined-by-this-fep","title":"Terms defined by this FEP","text":"In the associated context, we define the following terms:
"},{"location":"fep/a070/#orderedattachment","title":"orderedAttachment","text":"URI : https://www.w3.org/ns/activitystreams#attachment
Notes : https://www.w3.org/TR/activitystreams-vocabulary/#dfn-attachment
JSON-LD definition:
{\n \"@context\": {\n \"orderedAttachment\": {\n \"@id\": \"https://www.w3.org/ns/activitystreams#attachment\",\n \"@type\": \"@id\",\n \"@container\": \"@list\"\n }\n }\n}\n"},{"location":"fep/a070/#orderedtag","title":"orderedTag","text":"Term : orderedTag
URI : https://www.w3.org/ns/activitystreams#tag
Notes : https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tag
JSON-LD definition:
{\n \"@context\": {\n \"orderedTag\": {\n \"@id\": \"https://www.w3.org/ns/activitystreams#tag\",\n \"@type\": \"@id\",\n \"@container\": \"@list\"\n }\n }\n}\n"},{"location":"fep/a070/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/a4ed/","title":"FEP-a4ed: The Fediverse Enhancement Proposal Process","text":"Authors Status Date received Date final Tracking issue Discussions Repository pukkamustard pukkamustard@posteo.netFINAL 2020-10-16 2021-01-18 #201 - codeberg"},{"location":"fep/a4ed/#summary","title":"Summary","text":"A Fediverse Enhancement Proposal (FEP) is a document that provides information to the Fediverse community. The goal of a FEP is to improve interoperability and well-being of diverse services, applications and communities that form the Fediverse.
This document describes the scope, format and process of publishing Fediverse Enhancement Proposals.
"},{"location":"fep/a4ed/#scope-and-objectives","title":"Scope and Objectives","text":"A Fediverse Enhancement Proposal (FEP) should be a concise and focused documentation of a specific topic that is of interest to the Fediverse community.
A proposal should always have the intention of improving the interoperability and well-being of diverse services, applications and community of the Fediverse.
The Fediverse includes applications, services and communities using the ActivityPub protocol and other protocols that foster decentralized and diverse social media and culture.
Proposals may include descriptions of technical protocols and mechanisms, documentation of experimental work or current best practices.
Proposals are not limited to technical topics and may focus on social and cultural aspects.
Proposals may be entertaining and humorous (unlike this proposal).
"},{"location":"fep/a4ed/#language-document-structure-and-format","title":"Language, Document Structure and Format","text":"All Fediverse Enhancement Proposals must be written in English, be properly formatted as CommonMark and be reasonably grammatical.
Authors should use inclusive language and examples and refrain from using oppressive terminology.
"},{"location":"fep/a4ed/#proposal-title-and-identifier","title":"Proposal Title and Identifier","text":"Every Fediverse Enhancement Proposal must have a descriptive title.
An identifier is computed from the proposal title as the first 4 digits of the sha256 hash (in hex). The identifier can be computed from the title with standard Unix tools:
$ echo -n \"The Fediverse Enhancement Proposal Process\" | sha256sum | cut -c-4\na4ed\n By using the hash of the title as identifier we reduce the burden on facilitators to assign unique ids. This requires proposal titles to be unique.
"},{"location":"fep/a4ed/#proposal-type","title":"Proposal Type","text":"Fediverse Enhancement Proposals are classified into following types:
The type is chosen by the author. If the type is not specified, the proposal is assumed to be informational.
"},{"location":"fep/a4ed/#metadata","title":"Metadata","text":"Proposal metadata is placed at the top of the document as key-value pairs between opening and closing ~---~.
Following metadata key-value pairs may be placed at the top of a proposal:
type: Indicates the type of the proposal. Can be either informational or implementation.slug: the proposal identifier. The value must be enclosed within double quote marks, e.g. \"a4ed\".authors: A comma separated list of authors of the proposal. Each item of the list should contain an author's name followed by a contact address in angle brackets (e.g. Alyssa P. Hacker <alyssa.p.hacker@email.example>, Alyssa P. Hacker <@alyssa.p.hacker@fediverse.example>).status: Indicates the proposal status. Can be either DRAFT, WITHDRAWN or FINAL.dateReceived: Date of when the proposal was added to the repository (when status is set to DRAFT).dateWithdrawn: Date of when the proposal status was set to WITHDRAWN (only for proposals with status WITHDRAWN).dateFinalized: Date of when the proposal status was set to FINAL (only for proposals with status FINAL).trackingIssue: Link to the tracking issue for the proposal.discussionsTo: Link to the discussion.relatedFeps: A comma separated list of related FEPs (e.g. FEP-a4ed, FEP-141a, FEP-686f).replaces: A comma separated list of FEPs that are replaced by the proposal.replacedBy: Identifier of a FEP that replaces the proposal.Every FEP should include at least the following sections:
Following sections may be included in a proposal:
Fediverse Enhancement Proposals must be placed in the public domain by the authors with a CC0 1.0 Universal (CC0 1.0) Public Domain Dedication.
"},{"location":"fep/a4ed/#the-fediverse-enhancement-proposals-process","title":"The Fediverse Enhancement Proposals Process","text":"stateDiagram-v2\n accTitle: FEP process flowchart\n accDescr {\n The process begins from the \"DRAFT\" status, followed by either of the \"FINAL\" or \"WITHDRAWN\" status.\n The \"WITHDRAWN\" status can go back to the \"DRAFT\" status.\n }\n [*] --> DRAFT\n DRAFT --> FINAL\n DRAFT --> WITHDRAWN\n WITHDRAWN --> DRAFT\n FINAL --> [*] SUBMISSION.md file for a list of accepted submission methods.DRAFT and is added to the repository. The facilitator also creates a tracking issue for the proposal and adds trackingIssue attribute to the proposal metadata.slug, authors, status, dateReceived, trackingIssue and discussionsTo.FINAL or WITHDRAWN, facilitator adds dateFinalized or dateWithdrawn attributes to the proposal metadata.DRAFT:WITHDRAWN.FINAL.WITHDRAWN.FINAL can not be changed or updated in a way that would lead to adjustments to implementations. Minor corrections are allowed.Any substantial change to finalized proposal must be submitted as a separate FEP.
The new FEP MUST include a replaces metadata attribute pointing to FEPs it supersedes.
If the new FEP becomes FINAL, the original one MUST point to it in an added replacedBy metadata attribute.
A proposal with status WITHDRAWN remains in the repository and can be resubmitted.
FINAL a facilitator will archive all discussions linked in the tracking issue and add the resulting archive links to the tracking issue.A list of facilitators is maintained in the FACILITATORS.md file at the root of the FEP repository.
A list of methods in which a proposal may be submitted is maintained in the SUBMISSION.md file at the root of the FEP repository.
FEP-a4ed (The Fediverse Enhancement Proposal Process) is a living document and can be updated despite having the FINAL status.
Anyone can propose a change to FEP-a4ed (or other documents related to the FEP process) using any method listed in SUBMISSION.md file. The change must be approved by at least two facilitators (one of them could be the submitter). For any part of the FEP process, there must be a sufficient number of facilitators who agreed to do the maintenance work. Changes should not be accepted before at least 1 month passes after the last update, to give facilitators and the community time to review them and provide feedback.
Minor changes (i.e. a correction of a typo or of a broken link) can be accepted immediately after getting two approvals from facilitators.
"},{"location":"fep/a4ed/#history","title":"History","text":"The process and format described in this proposal is influenced by other community driven documentation efforts such as the BitTorrent Enhancement Proposal Process, Scheme Request for Implementation and the IETF RFC Series.
"},{"location":"fep/a4ed/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/a5c5/","title":"FEP-a5c5: Web Syndication Methods","text":"Authors Status Date received Tracking issue Discussions Repository AvidSeeker avidseeker7@protonmail.comDRAFT 2024-07-15 #368 Discussions codeberg"},{"location":"fep/a5c5/#summary","title":"Summary","text":"This document proposes a standard for web syndication methods across the Fediverse by appending .rss or .atom to object URLs. This will allow users to easily subscribe to feeds of timelines, posts, and other objects. Additionally, this proposal addresses whether syndication methods should be applicable to mirrored profiles across the Fediverse, recommending optional but preferred implementation.
To create a syndication feed, servers must append .rss or .atom to the URL of an object. For example:
https://example.org/@user.rss or https://example.org/@user.atomhttps://example.org/posts/12345.rss or https://example.org/posts/12345.atomFor mirrored profiles, such as https://example-mirror.org/@user@example.org, appending .rss or .atom should be supported, but it is optional. The recommendation is to implement this feature to maintain consistency and user convenience across different instances.
Example URLs:
https://example-mirror.org/@user@example.org.rss or https://example-mirror.org/@user@example.org.atomCC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/a974/","title":"FEP-a974: All Actor types should be followable","text":"Authors Status Date received Tracking issue Discussions Repository James Smith james@floppy.org.ukDRAFT 2025-02-05 #487 Discussions codeberg"},{"location":"fep/a974/#summary","title":"Summary","text":"In order to foster interoperability and good semantics, any valid unblocked Actor should be visible and followable on any platform when searched for. The type of the Actor should not matter for initial following, though can be used later as appropriate.
In order to federate, an ActivityPub service will expose a set of Actors; these are ActivityPub objects that (a) can perform activities and (b) can be followed (see the ActivityPub Specification).
Each Actor has a type. The Activity Vocabulary defines five \"core\" types: Person, Group, Service, Organization, and Application. These five types work well for many applications, but not all, and ActivityPub explicitly allows Actors to have any type.
In many services, it is desirable to use other object types for actors; either defined Object types or even custom types. For instance:
OrderedCollection, rather than a Group.OrderedCollection, or could even a custom Playlist type if the platform wanted to communicate specific semantics.However, some ActivityPub platforms may choose to only show actors of the five core types in search; if a platform wanted to use more semantically-meaningful types for its actors, those actors would be unavailable on such sites, which will inevitably lead to bug reports to the services using the non-core types.
While filtering at the Activity/Object type is sensible and inevitable for any ActivityPub platform, filtering at the Actor type level limits the ability of new services to use semantically-correct types as appropriate, and will over time make the type effectively useless, as new services will choose one of the five core types even if inappropriate, in order for their actors to be visible.
This issue has been discussed in the community before (see Mastodon issue #22322); this FEP attempts to move that discussion into a single simple statement of compatibility.
"},{"location":"fep/a974/#decision","title":"Decision","text":"Compliant ActivityPub services should not filter on Actor type in searches or actor-level activities (Follow, Accept, Undo, Block, etc). Any unblocked Actor should be followable on any service.
Services are of course free to filter activities that are delivered later on; this FEP puts no limit on that. The feed for an Actor that posts only Document object activities may appear completely empty to a user of a microblogging platform; it remains the right and privilege of any ActivityPub service to only process the activities it wants to.
However, by making all Actor types followable, new services can choose what activities to send where, safe in the knowledge that their actors will at least be visible and that the activities will be received.
A practical example: Manyfold allows Fediverse users to follow individual 3D Models (which could have a Document or even 3DModel actor type), and when they change, it posts Update activities where those models are the object. However, Manyfold knows that microblog applications don't understand those activities (and nor should they). Therefore, for compatibility, it sends what it terms a \"compabitility note\", a human-readable Create Note activity with the same information as the Update 3DModel, thus allowing microblog users to follow models and get updates wherever convenient. A decision on what to send where could use the compatibility detection proposed in FEP-9fde in future.
Potential negative impacts may happen if a service makes extra assumptions about an Actor based on its use of the core types (e.g. the background to Mastodon issue #22322 which talks about different semantics being used for Group actors), and discussion is invited on those impacts.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/ae0c/","title":"FEP-ae0c: Fediverse Relay Protocols: Mastodon and LitePub","text":"Authors Status Date received Date final Tracking issue Discussions Repository Steve Bate svc-fep@stevebate.netFINAL 2024-10-19 2025-03-14 #424 Discussions codeberg"},{"location":"fep/ae0c/#summary","title":"Summary","text":"Relays are important components within the decentralized Fediverse architecture. They act as intermediary servers that facilitate communication between different instances, enabling users on Fediverse platforms to share public content without requiring actor following relationships.
These relays benefit small instances by enabling them to effectively participate in the federated social network, both as consumers and producers of Fediverse content.
Several styles of relays existing in the Activity Fediverse. This FEP describe two popular styles of relays:
NOTE: This is an informational FEP documenting the current status quo. It uses RFC-2119 requirements keywords only as a convenience. Also, these are not standardized protocols. They will generally not be conformant with the ActivityPub standard although they use some concepts from it.
"},{"location":"fep/ae0c/#terminology","title":"Terminology","text":"For the purposes of this document, the following terminology will be used:
Term Description relay client actor An actor in a server that is a Relay Server subscriber. May also be referred to as a client actor. relay client server A server hosting one or more Relay Client Actors. May also be referred to as a client server. relay subscription A relationship established between a Relay Client Actor and a Relay Server using an ActivityPubFollow activity. relay server actor An actor in a server that provides relaying of messages between Relay Client Actors. May also be referred to as a relay actor. relay server A server hosting one or more Relay Server Actors. May also be referred to as a relay server or a relay. HTTP Signature HTTP-based signature (Cavage) used to verify message sender and contents. LD Signature JSON-LD signature used to verify messages regardless of transport."},{"location":"fep/ae0c/#mastodon-relay-protocol","title":"Mastodon Relay Protocol","text":"The Mastodon relay protocol relies on LD Signatures to verify relayed messages. This allows Mastodon to verify the relayed message although it is being sent by a different actor (the relay server actor).
"},{"location":"fep/ae0c/#relay-client-actor","title":"Relay Client Actor","text":"A Relay Client Actor establishes following relationship with a relay server actor and then processes relayed messages sent to the actor's ActivityPub inbox. The relay client server will add the relay inboxes to the delivery target for content with public visibility.
"},{"location":"fep/ae0c/#relay-subscription","title":"Relay Subscription","text":"Mastodon will POST an ActivityPub Follow request to the relay ActivityPub inbox URI. The object of the Follow request MUST be the fully expanded URI of the Public pseudo-collection (https://www.w3.org/ns/activitystreams#Public). The relay then responds to the Follow request with an Accept or Reject activity. The response time for the acknowledgement can be arbitrarily long since the subscription MAY require manual approval.
The request MUST be signed using the same HTTP Signature (Cavage) algorithms that Mastodon uses for ActivityPub federation. The relay will fetch the relay client actor document to obtain the actor's public key. For best interoperability, the actor ActivityPub document SHOULD be Mastodon-compatible. For example, preferredUsername SHOULD be provided in addition to all actor fields required by ActivityPub and the actor SHOULD provide a sharedInbox endpoint URL.
The relay client actor type SHOULD accurately reflect the actor type. However, note that some relay server implementations constrain the ActivityPub type of a client actor. For example, the relay server implementation might require the client actor to be an Application type and reject other types.
Example Follow Request
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://client.example/6ae15297\",\n \"type\": \"Follow\",\n \"actor\": \"https://client.example/actor\",\n \"object\": \"https://www.w3.org/ns/activitystreams#Public\"\n}\n Example Follow Accept Response
The Accept activity MAY respond with the accepted Follow activity URI as the object or it may embed a copy of the original Follow activity. A Reject activity will have a similar structure.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://relay.example/15c0b99f-23d4-4488-ba9d-d0c7bc2876a5\",\n \"type\": \"Accept\",\n \"actor\": \"https://relay.example/actor\",\n \"object\": {\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://client.example/6ae15297\",\n \"type\": \"Follow\",\n \"actor\": \"https://client.example/actor\",\n \"object\": \"https://www.w3.org/ns/activitystreams#Public\"\n }\n}\n"},{"location":"fep/ae0c/#relay-unsubscription","title":"Relay Unsubscription","text":"To unsubscribe from a relay send an Undo with the original Follow activity (embedded, or the URI) as the object. There is typically no response to the Undo.
Example Undo/Follow Request
{\n \"@context\": \"https: //www.w3.org/ns/activitystreams\",\n \"id\": \"https://client.example/3f5ebd6d\",\n \"type\": \"Undo\",\n \"actor\": \"https://client.example/actor\",\n \"published\": \"2024-10-14T14:42:17.650139+00:00\",\n \"object\": \"https://client.example/6ae15297\"\n}\n"},{"location":"fep/ae0c/#publishing-messages-to-a-relay","title":"Publishing Messages to a Relay","text":"To publish an activity to a Mastodon-style relay, the publisher MUST sign the message using the Mastodon-specific LD Signature algorithm. The benefit of using LD Signatures is that the receiving servers can verify the message content without refetching from the client server. This lowers the server load on the client server.
The disadvantage is that the LD Signatures are not easy to implement and Mastodon uses an outdated nonstandard form of the algorithm. The Mastodon documentation recommends not supporting LD Signatures for these reasons. Furthermore, the Mastodon documentation does not accurately describe the LD Signature algorithm it implements. For more details, see the additional information about Mastodon LD Signatures in this document.
The posted activity MUST be signed with a Mastodon-compatible HTTP Signature.
Mastodon will relay the following activity types: Create, Update, Delete, Move. A relay actor MAY only forward those types although Mastodon will accept other relayed activities, such as Announce, without an LD Signature. In the Announce case, it will fetch the announced object.
Relayed messages are posted to the relay client actor's inbox. Relayed messages MUST have an HTTP Signature signed by the Relay Actor.
Messages received from Relay Server Actors MAY have an LD Signature. If the HTTP Signature and the LD Signature are both present, the activity actor becomes the effective sender after LD Signature verification.
If no LD Signature is present and the received message is an Announce activity, then a relay client MUST ensure the content is legitimate (not spoofed). This may be done by fetching the announced activity from the originating server or using remote content from a local cache. However, if the announced activity is already locally cached, then there would typically be no processing to be done with it since it is already known to the client server.
A client server receiving a relayed message MAY also deliver the messages to local recipients based on the ActivityPub audience targeting properties.
"},{"location":"fep/ae0c/#relay-server-actor","title":"Relay Server Actor","text":"The following behaviors describe the typical implementation of a Mastodon-style relay server actor.
"},{"location":"fep/ae0c/#follow","title":"Follow","text":"Ensure that https://www.w3.org/ns/activitystreams#Public is in the object property. Use the actor as the relay client actor URI and save the information about the subscriber. The relay server MAY decide to deny access based on factors like the signer's domain.
Verify that the actor is a known relay client and, if so, remove the client actor from the set of relay actor followers.
When a message is received from a client actor, the relay MUST verify the HTTP Signature of the activity and identify the originating actor. If the message is valid, it is then posted (with the relay actor's HTTP Signature) to the inboxes of the relay's followers. No delivery is performed based on the ActivityPub audience targeting properties. The relay MUST not send the relayed message to the originating relay client actor.
Typically a message is forwarded unchanged. However, a relay MAY do other processing on the message, such as wrapping a message without an LD Signature using an ActivityPub Announce activity before forwarding it (See pub-relay). Extended behaviors like this are not described in this FEP.
A relay actor SHOULD only relay messages from followers. A relay actor SHOULD only relay activities that it hasn't already relayed. Addressing properties, like to, MUST be in a list even for a single URI.
Note that the Mastodon documentation for their LD Signatures is incomplete and inaccurate. This section provides more details, but it may be necessary to review the Mastodon source code for additional clarification.
An activity signed with a Mastodon LD Signature will have a signature document in the activity (using the signature property).
Example Signature Document
{\n \"@context\": [\n \"https: //www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/v1\"\n ],\n \"id\": \"https://client.example/3f5ebd6d\",\n # ...\n \"signature\": {\n \"type\": \"RsaSignature2017\",\n \"creator\": \"https://client.example/actor#main-key\",\n \"created\": \"2024-12-08T03:48:33.901Z\",\n \"signatureValue\": \"s69F3mfddd99dGjmvjdjjs81e12jn121Gkm1\"\n }\n}\n The https://w3id.org/security/v1 JSON-LD context defines the signature and related properties, but is not used by Mastodon for LD Signature processing.
When performing signature operations the signature document and the activity (without the signature document) are initially processed (hashed) separately. The SHA256 hash digests are concatenated that string is then signed.
"},{"location":"fep/ae0c/#signing-a-json-ld-activity","title":"Signing a JSON-LD Activity","text":"creator and created properties. Set the @context to https://w3id.org/identity/v1. (Note that this context doesn't appear to be accessible on the web any more. You may need a local copy with a custom JSON-LD context loader.) signatureValue of the signature document to the result. type to \"RsaSignature2017\". signature property of the activity to the signature document.signatureValue from the signature document.type and id and signatureValue properties from the signature document and generate a SHA256 hex digest for the modified signature document using the procedure described for signing documents.signature from the activity and generate a SHA256 hex digest for it.The LitePub protocol is based on ActivityPub and is used in Pleroma-compatible servers. The reference implementation is the Pleroma Relay.
"},{"location":"fep/ae0c/#relay-client","title":"Relay Client","text":"A LitePub relay client actor must have a type of Application and an actor ID ending with /relay. For best interoperability, it should be compatible with Mastodon actor documents and have WebFinger support. Other implementations may use different actor ID structures (e.g., AodeRelay apparently uses /actor and works with Pleroma). General relay interoperability of these LitePub variants is not known.
The client relay actor will send a Follow to the relay server. The Follow object is the relay server actor URI.
The relay server MUST respond to the Follow request with an Accept or Reject. If accepted, the relay server sends a reciprocal Follow request for the LitePub client actor. The client server SHOULD respond with Accept or Reject activity. A relay server MAY decide to ignore the subscription if no acknowledgement is received within a reasonable time interval.
Example Relay Follow Request
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://pleroma.example/schemas/litepub-0.1.jsonld\",\n {\n \"@language\": \"und\"\n }\n ],\n \"actor\": \"https://pleroma.example/relay\",\n \"bcc\": [],\n \"bto\": [],\n \"cc\": [],\n \"id\": \"https://pleroma.example/activities/3fe13910-73f4-4cdc-9c84-ec7013a3e764\",\n \"object\": \"https://relay.example/actor\",\n \"state\": \"pending\",\n \"to\": [\n \"https://relay.example/actor\"\n ],\n \"type\": \"Follow\"\n}\n Notes: 1. The JSON-LD context is not valid for JSON-LD processing. The litepub-0.1.jsonld document contains an invalid WebFinger-related context URL. 2. The state property is not defined in the JSON-LD context.
To unsubscribe from a relay send an Undo with the original Follow activity as the object. There is typically no response to the Undo.
Example Undo/Follow Request
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://pleroma.example/schemas/litepub-0.1.jsonld\",\n {\n \"@language\": \"und\"\n }\n ],\n \"id\": \"https://pleroma.example/activities/cf9c85e9-f83f-4a02-b598-880f15423f68\",\n \"object\": {\n \"actor\": \"https://pleroma.example/relay\",\n \"bcc\": [],\n \"bto\": [],\n \"cc\": [],\n \"context\": \"https://pleroma.example/contexts/d493d02b-7cc9-49dc-995c-d949af0b5417\",\n \"id\": \"https://pleroma.example/activities/3fe13910-73f4-4cdc-9c84-ec7013a3e764\",\n \"object\": \"https://relay.example/actor\",\n \"published\": \"2024-10-18T14:04:11.029802Z\",\n \"state\": \"cancelled\",\n \"to\": [\n \"https://relay.example/actor\"\n ],\n \"type\": \"Follow\"\n },\n \"published\": \"2024-10-18T14:04:11.029791Z\",\n \"to\": [ \"https://relay.example/actor\" ],\n \"cc\": [],\n \"type\": \"Undo\",\n \"actor\": \"https://pleroma.example/relay\",\n \"context\": \"https://pleroma.example/contexts/d493d02b-7cc9-49dc-995c-d949af0b5417\"\n}\n"},{"location":"fep/ae0c/#publishing-messages-to-a-relay_1","title":"Publishing Messages to a Relay","text":"A LitePub relay client actor will send an Announce for a relayed object (like a Note). For best interoperability Announce should refer to the announced object using an URI (instead of embedding the object).
The Announce activity MUST be address to the relay server actor's followers collection. (TODO it's not known if the admin addressing is also required). The published property should be included since some relay server will reject activities without it.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://pleroma.example/schemas/litepub-0.1.jsonld\",\n {\n \"@language\": \"und\"\n }\n ],\n \"actor\": \"https://pleroma.example/relay\",\n \"to\": [\n \"https://pleroma.example/relay/followers\",\n \"https://pleroma.example/users/admin\"\n ],\n \"bto\": [],\n \"cc\": [],\n \"context\": \"https://pleroma.example/contexts/a59117d9-7f7c-48ec-83b4-5e183e7179b5\",\n \"id\": \"https://pleroma.example/activities/e24e46a2-8926-4a20-9f5f-638e06102159\",\n \"object\": \"https://pleroma.example/objects/c13bba3c-e7c1-45ac-939f-aa292d23ee8c\",\n \"published\": \"2024-10-18T14:06:37.736295Z\",\n \"type\": \"Announce\"\n}\n"},{"location":"fep/ae0c/#receiving-messages-from-a-relay","title":"Receiving Messages from a Relay","text":"Messages received from a relay are typically wrapped in an Announce activity. AFter the object of the announce is fetched and validated, it is displayed on the federated timeline. It appears that Pleroma will accept a relayed Create activity (for Mastodon compatibility), but will refetch the Create object since the LD Signature isn't processed. (TODO verify this behavior.)
The relay server hosting relay actors will have other functionality other than activity relaying.
"},{"location":"fep/ae0c/#webfinger","title":"WebFinger","text":"A relay server MUST implement WebFinger support for relay actors. This is necessary because of Mastodon's actor fetching implementation. It's possible that it's not required for LitePub-only relay servers.
"},{"location":"fep/ae0c/#nodeinfo","title":"NodeInfo","text":"A relay server MAY implement NodeInfo to advertise server activity and metadata.
"},{"location":"fep/ae0c/#optional-relay-server-behaviors","title":"Optional Relay Server Behaviors","text":"A relay server MAY support multiple relay protocols. However, there is no standard way to advertise those capabilities.
A relay server often hosts a single actor, but any number of relay actors may be hosted. For example, a relay server may have a relay actor for specific topics, hashtags, or moderation categories. A relay client can subscribe to any number of relay actors in a given server.
Some servers implement dynamic relay actor creation. A relay actor's inbox URI might be based on a hashtag or a topic name. When a client actor subscribes to this kind of inbox URI, the relay actor is created automatically. Obviously, there are risks to this approach when used by misbehaved clients.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/ae97/","title":"FEP-ae97: Client-side activity signing","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2023-08-14 #148 Discussions codeberg"},{"location":"fep/ae97/#summary","title":"Summary","text":"Existing Fediverse servers manage signing keys on behalf of their users. This proposal describes a new kind of ActivityPub client that lets users sign activities with their own keys, and a server that can distribute client-signed activities to other servers.
"},{"location":"fep/ae97/#history","title":"History","text":"Initial version of this proposal relied on linking of cryptographic identities to actor objects via FEP-c390 identity proofs. That mechanism was superseded by FEP-ef61 which achieves full data portability.
"},{"location":"fep/ae97/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/ae97/#discovery","title":"Discovery","text":"A server that supports clients capable of managing signing keys MUST have a discovery endpoint at the path /.well-known/apgateway.
When the server receives an HTTP GET request to this endpoint, it MUST respond with a JSON object containing information about itself. That object MAY be empty.
Example:
{\n \"uploadMedia\": \"https://gateway.example/.well-known/apgateway-media\"\n}\n"},{"location":"fep/ae97/#registering-an-actor","title":"Registering an actor","text":"Client creates portable actor objects according to FEP-ef61 and stores them. When a portable actor is created, the client MUST create a key for signing requests (the main actor key) and add a Multikey representation of it to the assertionMethod array as described in FEP-521a. The identifier of that key MUST NOT be a compatible identifier.
Before registering a portable actor on the server, the client MUST add the server URL to the gateways array of the actor object.
To register the actor, the client sends an HTTP POST request to the gateway endpoint at /.well-known/apgateway path. The body of the request MUST be an actor object.
The server SHOULD limit registrations (for example, by requiring an invite code). If the server accepts the registration request, it generates an RSA key and returns it in a response. The response MUST have the 201 Created status code. The body of the response is a JSON object with an assertionMethod property. The value of this property is an array containing the RSA public key generated by the server in the Multikey format.
Example:
{\n \"assertionMethod\": [\n {\n \"type\": \"Multikey\",\n \"publicKeyMultibase\": \"z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i\"\n }\n ]\n}\n If the server can't register the actor, it MUST return a 400 Bad Request status code.
If the registration is successful, the client MUST attach the RSA key to the actor object via publicKey property, and also add it to the assertionMethod array as described in FEP-521a. If the server's response contains other keys, they SHOULD be added to the assertionMethod array as well.
If the client uses compatible identifiers, then key identifiers MUST be generated with the server's origin.
After updating the actor object, the client MUST publish an Update activity for it.
The client submits signed FEP-ef61 activities to actor's outbox. Contrary to what ActivityPub specification prescribes in section 6. Client to Server Interactions, the server MUST NOT overwrite the ID of an activity. Instead of assigning a new ID, the server MUST verify that provided ID has not been used before. If the server accepts activity, its response MUST have 202 Accepted status code.
If activity contains a wrapped object (as in Create and Update activities), it MUST be a portable object created according to FEP-ef61. The server MUST validate object IDs in the same way it validates activity IDs.
The server MUST deliver activities to their indended audiences without altering them. When signing HTTP requests, the server uses the RSA key generated during the registration.
If the owner of the outbox is not registered, the server MUST return a 404 Not Found status code.
If the actor of the submitted activity is different from the outbox owner, the server MUST return a 403 Forbidden status code.
Client receives activities by polling the actor's inbox.
Requests to inbox endpoint MUST have an HTTP signature created using the main actor key (the one generated by the client, not a server-generated key).
If the signature is not valid, the server MUST return a 401 Unauthorized status code.
If the owner of the inbox is not registered, the server MUST return a 404 Not Found status code.
If the actor that generated the signature is different from the inbox owner, the server MUST return a 403 Forbidden status code.
To upload a media file, the client sends an HTTP POST request to the gateway endpoint at the /.well-known/apgateway-media path. The body of the request MUST be the media in the form of binary data. The request MUST contain a Content-Type header and MUST be signed with the main key of the actor.
If the media is processed successfully, the server MUST return a response with a 201 Created status code and a JSON object as the body. This JSON object MUST have a url property whose value is a hashlink to the processed media file.
Example:
{\n \"type\": \"Document\",\n \"url\": \"hl:zQmWvQxTqbG2Z9HPJgG57jjwR154cKhbtJenbyYTWkjgF3e\"\n}\n The server MUST serve the processed media file at a /.well-known/apgateway-media/{hashlink} path.
If the signature is not valid, the server MUST return a 401 Unauthorized status code. If the signature is valid, but the actor is not registered, the server MUST return a 403 Forbidden status code.
If the media type is not supported, the server MUST return a 400 Bad Request status code.
If the media is too large, the server MUST return a 413 Payload Too Large status code.
To delete a previously uploaded media file, the client sends an HTTP DELETE request to an endpoint at path /.well-known/apgateway-media/{hashlink}. The request MUST be signed with the main key of the actor.
If the signature is valid and the actor owns the media file, the server MUST delete the file.
If the signature is not valid, the server MUST return a 401 Unauthorized status code. If the signature is valid, but the actor is not registered, the server MUST return a 403 Forbidden status code.
If the media file doesn't exist or not owned by the actor, the server MUST return a 404 Not Found status code.
If the server accepts portable objects with compatible identifiers, it MUST ensure that all objects served by the gateway are valid and that registered actors are sufficiently isolated from each other. Specifically, the server MUST verify that actors and activities generated by the client are permitted in the origin-based security model:
Precautions need also be taken when objects with compatible IDs are delivered to inboxes or fetched from remote servers.
"},{"location":"fep/ae97/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/b06c/","title":"FEP-b06c: ActivityPoll","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.orgDRAFT 2025-06-25 #638 Discussions codeberg"},{"location":"fep/b06c/#summary","title":"Summary","text":"ActivityPoll is a proper subset of ActivityPub that excludes activity delivery, making it easier to implement for static Web sites or content management systems. It meets an equivalent need to RSS or Atom feeds.
"},{"location":"fep/b06c/#introduction","title":"Introduction","text":"In ActivityPub (AP), an actor is an ActivityPub object that produces activities. The actor's outbox is the collection of activities that the actor has produced.
ActivityPub has optional features for real-time delivery of activities. ActivityPub actors MAY deliver new activities to the actors addressed in the activity's to, cc, bto, bcc and audience properties using the ActivityPub federation protocol. The actor MAY also receive activities from other actors in its inbox, such as comments, likes, and other feedback.
Supporting the federation protocol adds significant complexity in the implementation of ActivityPub. Handling incoming and outgoing activities is a significant barrier to entry for AP implementation.
One alternative is to shift the burden of initiating delivery from the publisher to the consumer. Instead of pushing activities from the publishing server as they are generated, the activities can instead be pulled by the consumer at regular intervals.
There are three main downsides to a polling structure.
inbox.The primary upside is that polling opens up the social web to many more implementers. Any publisher that can publish an RSS feed can publish an ActivityPoll actor. Polling also opens up the network to simpler, power-constrained devices, as well as static web sites.
Having more content producers is better for all users, and thus better for more mature and full-featured social web platforms. Shifting this burden of delivery, if it results in more information on the network, can be good for the entire ecosystem.
This subset of ActivityPub, called ActivityPoll, describes the reduced responsibilities of the publisher, the increased responsibilities of the consumer, and methods for indicating use of polling.
"},{"location":"fep/b06c/#publishers","title":"Publishers","text":"ActivityPoll publishers host one or more ActivityPoll actors. ActivityPoll actors are valid ActivityPub actors and ActivityPub objects. They MUST have at least the following properties:
id: A unique identifier for the actor, which MUST be a dereferenceable IRI.type: The type of the actor.outbox: An OrderedCollection of activities, with its own dereferenceable IRI.inbox: An OrderedCollection of activities, with its own dereferenceable IRI.Other properties of an Activity Streams 2.0 object MAY be included, including extension properties.
Other properties of an ActivityPub actor object MAY be included.
"},{"location":"fep/b06c/#inbox","title":"Inbox","text":"If an actor is poll-only, and does not support the ActivityPub federation protocol, the IRI of the inbox property of the actor MUST respond with a 405 Method Not Allowed response to HTTP POST requests.
Publishers MAY also indicate that delivery is not supported using the pollOnly property defined in this specification, setting it to true. This does not override the requirement to have an inbox property that returns a 405 HTTP status on POST.
The actor's outbox property is an OrderedCollection in reverse chronological order. It MAY be paginated.
The IRI for the outbox collection SHOULD support HTTP Caching, including the If-Modified-Since and If-None-Match request headers. The IRI for each page of a paginated outbox collection SHOULD support HTTP Caching.
The outbox collection object SHOULD include a totalItems property, so that changes in any page of the collection are reflected in the collection itself.
Activities in the outbox, and all the object properties of the activities, MUST be valid ActivityPub objects, with dereferenceable IRIs.
To make incremental polling manageable, ordering of activities in the outbox collection SHOULD be stable. New activities SHOULD be prepended to the collection. Deleted activities SHOULD be replaced by a Tombstone object.
ActivityPoll servers MAY require authentication, such as OAuth 2.0 or HTTP Signatures. Collections such as outbox MAY be filtered according to the access level of the authenticated client.
Without an inbox that can receive incoming activities, an ActivityPoll publisher might not maintain many of the state changes that are typical side effects of such activities. A non-exhaustive list of collections affected by inbound activities:
followers collection for an actorreplies collection for an objectshares collection for an objectlikes collection for an objectPublishers SHOULD omit these properties from objects and actors if they will not be maintained.
"},{"location":"fep/b06c/#webfinger","title":"Webfinger","text":"ActivityPoll servers MAY implement Webfinger to allow user@hostname addressing for actors. Because Webfinger requires different content based on a query parameter, it is difficult to implement for static web sites, unless they only support a single actor.
ActivityPoll consumers read the outbox collection of an actor to retrieve activities and distribute them.
ActivityPoll consumers SHOULD support HTTP Caching when fetching the OrderedCollection, its pages, and any activities and related objects in the collection.
Because the outbox property is ordered in reverse chronological order, consumers SHOULD use the results of their last poll to limit requests to the publishing server. Keeping a \"last read\" activity ID between polls can be used to limit the requests to only activities and pages published since that ID.
ActivityPoll consumers SHOULD include remote ActivityPoll actors in their actors' following collections.
Polling frequency in syndication systems is important. If polling is too infrequent, new activities aren't processed in a timely fashion. If polling is too frequent, it abuses publisher and consumer resources without any new activities being delivered.
The updateInterval property of an actor can indicate how often the actor generates activities. Consumers SHOULD use this information to schedule polling requests. Consumers MAY use heuristics to schedule polling requests, such as median time between activities previously seen. Consumers MAY set a minimum frequency for polling actors, such as daily or monthly.
If the addressing properties of an activity by an ActivityPoll actor include actors on the consumer server, the consumer SHOULD deliver the activities to those actors' inbox collections. ActivityPoll consumers MAY deliver activities by ActivityPoll publisher actors to other servers, for example for inbox forwarding.
Some activities, or their objects, MAY be added to special collections on the consumer server. These include:
inReplyTo property with the value of an ActivityPub object on the consuming server MAY be added to the replies collection for that object.Like activities with an object value on the consumer server MAY be added to the likes collection of that object.Announce activities with an object value on the consumer server MAY be added to the shares collection of that object.Follow activities with an object value on the consumer server MAY result in the actor being added to the object value's followers collection.Other side effects MAY be implemented by the consuming server.
"},{"location":"fep/b06c/#context","title":"Context","text":"This specification includes a context document that defines the optional properties of an ActivityPoll actor.
{\n \"@context\": {\n \"poll\": \"https://w3id.org/fep/b06c#\",\n \"pollOnly\": \"poll:pollOnly\",\n \"updateInterval\": \"poll:updateInterval\"\n }\n}\n The context can be included using the context URL https://w3id.org/fep/b06c, as illustrated in the examples below.
pollOnly","text":"This is a boolean property that can be used to determine if an actor does not deliver activities remotely. Its default value is false.
updateInterval","text":"This property represents a typical interval between activities in the outbox of the actor. It is expressed as an xsd:duration value. Some example values:
P15M - new activities are added every 15 minutesP4H - new activities are added once every 4 hoursP1D - updated dailyP3M - updated quarterlyP1Y - updated annuallyThis interval conveys a rough estimate of update frequency, not a commitment. Different types of actors often have different posting profiles. Automated actors often have regular intervals for activities. Real-world humans, on the other hand, tend to cluster into \"sessions\", when a human being makes a number of activities in only a few minutes or hours.
"},{"location":"fep/b06c/#examples","title":"Examples","text":""},{"location":"fep/b06c/#minimal-activitypoll-actor","title":"Minimal ActivityPoll Actor","text":"This minimal actor provides just enough information to be a useful ActivityPoll object.
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/b06c\"],\n \"id\": \"https://device.example/actor.jsonld\",\n \"type\": \"Application\",\n \"name\": \"Low-power device #69883\",\n \"to\": \"as:Public\",\n \"pollOnly\": true,\n \"inbox\": \"https://device.example/actor/inbox.jsonld\",\n \"outbox\": \"https://device.example/actor/outbox.jsonld\"\n}\n The inbox for this actor is an empty collection.
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/5711\"],\n \"id\": \"https://device.example/inbox.jsonld\",\n \"type\": \"OrderedCollection\",\n \"summary\": \"Inbox of low-power device #69883\",\n \"attributedTo\": \"https://device.example/actor.jsonld\",\n \"to\": \"as:Public\",\n \"inboxOf\": \"https://device.example/actor.jsonld\",\n \"totalItems\": 0\n}\n The outbox for this actor is not paginated.
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/5711\"],\n \"id\": \"https://device.example/outbox.jsonld\",\n \"type\": \"OrderedCollection\",\n \"summary\": \"Outbox of low-power device #69883\",\n \"attributedTo\": \"https://device.example/actor.jsonld\",\n \"to\": \"as:Public\",\n \"outboxOf\": \"https://device.example/actor.jsonld\",\n \"totalItems\": 5,\n \"items\": [\n \"https://device.example/create-note-3.jsonld\",\n \"https://device.example/like-note-1.jsonld\",\n \"https://device.example/delete-note-2.jsonld\",\n \"https://device.example/create-note-2.jsonld\",\n \"https://device.example/create-note-1.jsonld\"\n ]\n}\n"},{"location":"fep/b06c/#publication","title":"Publication","text":"This example models a simple ActivityPoll actor for a Web magazine. By embedding node objects into the document, the publisher can dramatically reduce the number of HTTP requests required to check for updates.
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/b06c\"],\n \"id\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"type\": \"Organization\",\n \"name\": \"ActivityPoll Magazine\",\n \"summary\": \"The premier magazine for the poll-oriented social web\",\n \"pollOnly\": true,\n \"updateInterval\": \"P1D\",\n \"inbox\": \"https://publisher.example/magazine/activitypoll/inbox.jsonld\",\n \"outbox\": {\n \"id\": \"https://publisher.example/magazine/activitypoll/outbox.jsonld\",\n \"totalItems\": 4,\n \"first\": {\n \"id\": \"https://publisher.example/magazine/activitypoll/outbox/1.jsonld\",\n \"orderedItems\": [\n {\n \"id\": \"https://publisher.example/magazine/activitypoll/create/article/3.jsonld\",\n \"type\": \"Create\",\n \"summary\": \"ActivityPoll Magazine created an article\",\n \"actor\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\",\n \"object\": {\n \"id\": \"https://publisher.example/magazine/activitypoll/article/3.jsonld\",\n \"type\": \"Article\",\n \"name\": \"Polling strategies for ActivityPoll consumers\",\n \"summary\": \"<p>An overview of strategies for polling intervals</p>\",\n \"url\": \"https://publisher.example/magazine/activitypoll/article/3.html\",\n \"attributedTo\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\"\n },\n \"published\": \"20250621T00:00:00Z\"\n },\n {\n \"id\": \"https://publisher.example/magazine/activitypoll/delete/article/2.jsonld\",\n \"type\": \"Delete\",\n \"summary\": \"ActivityPoll Magazine deleted an article\",\n \"actor\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\",\n \"object\": {\n \"id\": \"https://publisher.example/magazine/activitypoll/article/2.jsonld\",\n \"type\": \"Tombstone\",\n \"formerType\": \"Create\",\n \"published\": \"20250615T00:00:00Z\",\n \"deleted\": \"20250618T00:00:00Z\",\n \"attributedTo\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\"\n },\n \"published\": \"20250618T00:00:00Z\"\n },\n {\n \"id\": \"https://publisher.example/magazine/activitypoll/create/article/2.jsonld\",\n \"type\": \"Create\",\n \"summary\": \"ActivityPoll Magazine created an article\",\n \"actor\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\",\n \"object\": {\n \"id\": \"https://publisher.example/magazine/activitypoll/article/2.jsonld\",\n \"type\": \"Tombstone\",\n \"formerType\": \"Create\",\n \"published\": \"20250615T00:00:00Z\",\n \"deleted\": \"20250618T00:00:00Z\",\n \"attributedTo\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\"\n },\n \"published\": \"20250615T00:00:00Z\"\n },\n {\n \"id\": \"https://publisher.example/magazine/activitypoll/create/article/1.jsonld\",\n \"type\": \"Create\",\n \"summary\": \"ActivityPoll Magazine created an article\",\n \"actor\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\",\n \"object\": {\n \"id\": \"https://publisher.example/magazine/activitypoll/article/1.jsonld\",\n \"type\": \"Article\",\n \"name\": \"Announcing ActivityPoll Magazine\",\n \"summary\": \"<p>This is a new magazine for posts about the poll-oriented social web.</p>\",\n \"url\": \"https://publisher.example/magazine/activitypoll/article/1.html\",\n \"attributedTo\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\"\n },\n \"published\": \"20250609T00:00:00Z\"\n }\n ]\n }\n }\n}\n"},{"location":"fep/b06c/#social-network-interactions","title":"Social Network Interactions","text":"This example models a simple social network user, with reaction activities to content published elsewhere.
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/b06c\"],\n \"id\": \"https://social.example/user/activitypoll.jsonld\",\n \"type\": \"Person\",\n \"name\": \"Social Networkuser\",\n \"summary\": \"A typical social network user\",\n \"pollOnly\": true,\n \"updateInterval\": \"P8H\",\n \"inbox\": \"https://social.example/user/activitypoll/inbox.jsonld\",\n \"outbox\": {\n \"id\": \"https://social.example/user/activitypoll/outbox.jsonld\",\n \"totalItems\": 3,\n \"first\": {\n \"id\": \"https://social.example/user/activitypoll/outbox/1.jsonld\",\n \"orderedItems\": [\n {\n \"id\": \"https://social.example/user/activitypoll/activity/3.jsonld\",\n \"type\": \"Like\",\n \"summary\": \"Social Networkuser liked this object\",\n \"object\": \"https://other.example/object/D6D11370-8BD1-4E52-849B-AF62121AB58E\"\n },\n {\n \"id\": \"https://social.example/user/activitypoll/activity/2.jsonld\",\n \"type\": \"Announce\",\n \"summary\": \"Social Networkuser shared this object\",\n \"object\": \"https://third.example/object/50D4C4E9-DEB4-4EC5-8353-F079A63533F0\"\n },\n {\n \"id\": \"https://social.example/user/activitypoll/activity/3.jsonld\",\n \"type\": \"Create\",\n \"summary\": \"Social Networkuser created a note\",\n \"object\": {\n \"id\": \"https://social.example/user/activitypoll/note/1.jsonld\",\n \"type\": \"Note\",\n \"inReplyTo\": \"https://other.example/object/0BDF12F8-1148-4BDF-B911-3B9CBEA95678\",\n \"content\": \"I agree!\"\n }\n }\n ]\n }\n }\n}\n"},{"location":"fep/b06c/#privacy-considerations","title":"Privacy considerations","text":"This specification allows publishers to implement authentication for ActivityPub objects, actors, and collections. However, authentication is difficult to implement for static web sites. Features that depend on authentication, such as non-public objects or blocking users or domains, will be hard to implement or enforce with ActivityPoll.
"},{"location":"fep/b06c/#implementation-notes","title":"Implementation notes","text":"The subset of ActivityPub outlined in this specification does not preclude the use of the ActivityPub API for publishing activities, even if they are not delivered to remote addressees.
"},{"location":"fep/b06c/#history","title":"History","text":"Read-only, poll-based syndication feeds are a common pattern on the Web. RSS 1.0, RSS 2.0, Atom, and other syndication formats have been used for decades to provide a simple way to transfer content from one Web site to another. Atom Activity Streams 1.0 enhanced Atom with richer data about social networking activities, such as comments, likes, and shares.
"},{"location":"fep/b06c/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/b2b8/","title":"FEP-b2b8: Long-form Text","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.orgDRAFT 2024-11-07 #441 Discussions codeberg"},{"location":"fep/b2b8/#summary","title":"Summary","text":"Multi-paragraph text is an important content type on the Social Web. This FEP defines best practices for representing and using properties of a long-form text object in Activity Streams 2.0.
"},{"location":"fep/b2b8/#motivation","title":"Motivation","text":"Blog posts, magazine articles, and forum posts are often made up of multiple paragraphs of text, sometimes with embedded images, video, audio or other media. This important content type is documented in the Activity Vocabulary, but this FEP provides additional guidance for publishers and consumers and collects the relevant properties in one place.
Well-defined behaviour for supporting long-form text provides multiple benefits. Not only does it allow publishers to integrate content in a dependable way across different platforms, but it also gives users of Activity Streams 2.0 consumer applications more control over their reading experience -- including filtering or sorting long-form text objects in their stream.
This FEP does not provide guidance for book-length or longer text.
This document provides information for multiple protocols that use Activity Streams 2.0 as a representation format. Where ActivityPub use is different than AS2, it is noted.
Because long-form text is often syndicated using RSS 2.0, the properties in this FEP are compared to the properties in that format where appropriate.
"},{"location":"fep/b2b8/#type","title":"Type","text":"The Article type is used to represent multi-paragraph text. The Activity Streams 2.0 primer provides guidance on when to use the Article type and when to use the Note type.
Some consumers do not display Article objects with their full content. Some publishers work around this by using a Note-type object with much more content than expected for a note.
Publishers should avoid this workaround, and instead give consumers the full information they need to display the content correctly in their own interfaces. The preview property can be used to provide a simpler version of the content for consumers that don't support Article directly.
Forcing long-form text into a Note object can cause problems for consumers that expect Note objects to be short and well-formatted for stream display. Maintaining a clear distinction between Note and Article objects is important for interoperability.
Consumers that only display short text should show the name, summary and a link to the url property so that users can view the full content in a web browser. As a fallback, they can use the preview property if it is present.
id","text":"A unique identifier for the text. For ActivityPub, this should be an HTTPS URL that resolves to the object. It should be a single string, unique for all objects.
This property provides the same functionality as the guid property in RSS 2.0.
name","text":"The title of the text should be in the name property. The property should be short enough to be displayed in a line or two on a browser interface; 75-150 characters is a good rule of thumb. Longer descriptions should be in the summary property.
The name property should be plain text, not HTML or other markup. In particular, no HTML entities like & or < should be used.
This property provides the same functionality as the title property in RSS 2.0.
url","text":"The location of the full text should be in the url property. This can be a single string, in which case it is the URL of the HTML representation of the text. It can also be a Link object, which can include additional metadata about the link. If it is a Link object, the mediaType should be 'text/html' and href property should be the URL of the HTML representation of the text.
The url property can also be an array of strings or Link objects or both. Multiple Link objects can be used to represent different media types or provide different URL protocols. At least one of the Link objects should have a mediaType of 'text/html' and a href property with the protocol 'https'.
This property provides the same functionality as the link property in RSS 2.0.
summary","text":"This property provides a brief description, teaser, abstract or \"lede\" for the text. It should be a maximum of about 500 characters; a few sentences; or a short paragraph.
This property can include HTML markup. It should not include embedded media like images, video or audio. It should not include navigation or interaction elements like \"favourite\", \"like\", \"bookmark\" or other buttons. It should not include links to the publisher's home page or category pages. It should not include a \"Read more...\" link to the full text.
This property provides the same functionality as the description property in RSS 2.0.
attributedTo","text":"This property provides the authors of the text, either as a string, an object, or an array.
As a string, it is a single id for the author. For ActivityPub, the id should be a URL that resolves to an ActivityPub actor.
As an object, it can be an AS2 object with a type like Person, Application or Organization. It should have an id and a name property and can also include an icon property for the author's avatar. A summary property can be used to provide a brief description of the author, including HTML. An url property can be used to provide a link to the author's profile page.
If the author does not have an AS2 representation, the attributedTo property can be an object with a type of Link and an href property with the URL of the author's profile page. The name property can be used to provide the author's name.
As an array, the attributedTo property can include multiple authors, either as strings or objects.
The attributedTo property provides the same functionality as the author property of an item in RSS 2.0, with additional features.
published","text":"The publication date of the text should be in the published property. This should be a dateTime string in the format YYYY-MM-DDTHH:MM:SSZ.
This property provides the same functionality as the pubDate property in RSS 2.0.
updated","text":"If the object has been updated, the date of the last update should be in the updated property. This should be a dateTime string in the format YYYY-MM-DDTHH:MM:SSZ. If the property is not present, consumers can assume that the object has not been modified since the published date.
image","text":"The image property provides a notable or representative image for the text. It can be included by reference as an id or with an Image type object.
There can be multiple values for the image property, either as an array of id strings or Image objects. Publishers should provide these in order of importance, with the most important image first. Consumers can use as many or as few as needed.
content","text":"The full text of the article or blog post should be in the content property. This should be HTML. Using mediaType to set a different media type presumes that consumers will be able to display that type.
The HTML elements in the content property should include a sanitized subset of the full HTML element set. It should not include any CSS or JavaScript. This subset should include:
<p><span> (class)<h2>, <h3>, <h4>, <h5>, <h6><br><a> (href, rel, class)<del><pre><code><em><strong><b><i><u><ul><ol> (start, reversed)<li> (value)<blockquote><img> (src, alt, title, width, height, class)<video> (src, controls, loop, poster, width, height, class)<audio> (src, controls, loop, class)<source> (src, type)<ruby><rt><rp>The HTML should only include the content of the text. Additional navigation to other pages on the originating site, like category links or home page links, should not be included. Other affordances like \"favourite\", \"like\", \"bookmark\" or other buttons should not be included. It should not include a \"Read more...\" link to the full article.
Any embedded media like images, video or audio in the content property should also be listed in the attachment property so that consumers can pre-fetch the media.
source","text":"If the text was originally created in a different format, the original source should be in the source property to allow editing the content. It should include the mediaType of the source format and the content property with the original content.
replies","text":"Comments on the text should be linked in the replies property. This should be a URL that resolves to a collection of objects for the replies.
Comments are usually Note objects, but can be other types of objects like Article or Question.
This property provides the same functionality as the comments property in RSS 2.0.
inReplyTo","text":"If the text is a reply to another ActivityPub object, such as an Article or Note, the inReplyTo property can include a string with the URL of the object being replied to, or a JSON object representing that object.
If the text is a commentary on or review of a particular link on the Web, the inReplyTo property can include a Link object with a href property that is the URL of the linked resource.
attachment","text":"The attachment property provides additional media that is part of the text. This can include images, video, audio, or other media. Consumers can use this property to pre-fetch media for display without needing to load and parse the full content property.
tag","text":"The tag property provides additional metadata about the text. There are two important types of tags:
name property with the tag text.href property with the URL of the actor's profile page.context","text":"If the text is part of a larger collection, the context property can provide a link to the collection. An example might be an article in a series, a newspaper column, a blog category (although tag may be better here) or a section of a magazine.
There can be multiple context properties, either as an array of strings or objects or both.
generator","text":"The generator property provides information about the software that generated the text. This is usually an Application or Service object with an id and a name property.
preview","text":"In AS2, the preview property provides an abbreviated version of the content of the object. Especially for microblogging applications, the preview property is a useful fallback for supporting unrecognized object types like Article.
For an article, the preview can be a Note that gives a well-formatted preview of the article content in its content property. For example, the name and summary. The preview content SHOULD NOT include a link to the HTML representation for the article. Additional navigation to other pages on the originating site, like category links, home page links, and other affordances like \"favourite\", \"like\", \"bookmark\" or other buttons should not be included.
The content property of the preview should include a minimal set of HTML elements, as described in ActivityPub Primer HTML.
Metadata on the Article that applies equally to the preview, such as attributedTo, published, updated, and tag can be repeated in the preview property. The consumer should fall back to the Article properties if they are not present in the preview.
The image property of the Article may be included in the preview property as attachment items.
The preview property may have an id property.
to, cc, bcc, bto, audience","text":"As with other AS2 object types, the to, cc, bcc, bto, and audience properties identify the addressees of the text. For ActivityPub, they also determine the delivery targets of the text.
The addressing properties provide an access control mechanism for AS2. Publishers and consumers should not disclose the properties of any AS2 object type, including the Article type, with anyone except the addressees, listed in these addressing properties, or the creator(s), listed in the attributedTo property.
sensitive","text":"sensitive marks an article as potentially sensitive, controversial, or disturbing in the author's opinion. As a non-exhaustive list and depending on context, nudity, sexual activity, violence, or spoilers for a movie or book may be considered sensitive.
If the sensitive flag is set, the consumer should obscure the content of the article until the user conveys intent to read the article or view embedded media.
To help the user decide whether to read the article or view its media, the consumer should show these properties, if provided, in order:
dcterms:subject: a property from the Dublin Core metadata terms. Value should be a string or array of strings indicating the topic or topics discussed in the article.tag: in particular, Hashtag names. (subject is preferred because hashtags are often less human-readable).name: as described above, the title of the Article. Authors are more likely to leak sensitive material in the title, so the subject and/or hashtags should be used first.summary: as described above. Only as a last resort when other properties are not defined; a well-written summary is likely to include significant excerpts or summation of the sensitive content.This section includes examples of long-form text objects. Note that for brevity, the content is not actually multi-paragraph text.
"},{"location":"fep/b2b8/#long-form-text-with-included-content","title":"Long-form text with included content","text":"{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.com/2024/11/07/long-form-text.jsonld\",\n \"name\": \"Long-form text with included content\",\n \"url\": \"https://example.com/2024/11/07/long-form-text.html\",\n \"attributedTo\": \"https://example.com/evan\",\n \"summary\": \"<p>This is a long-form text object with included content. It has a title, a summary, and a full text.</p>\",\n \"content\": \"<p>This is a long-form text object with included content. It has a title, a summary, and a full text.</p>\",\n \"published\": \"2024-11-07T12:00:00Z\"\n}\n"},{"location":"fep/b2b8/#long-form-text-with-external-content","title":"Long-form text with external content","text":"{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.com/2024/11/07/long-form-text-no-content.jsonld\",\n \"name\": \"Long-form text with included content\",\n \"url\": \"https://example.com/2024/11/07/long-form-text-no-content.html\",\n \"attributedTo\": \"https://example.com/evan\",\n \"summary\": \"<p>This is a long-form text object with external content. It has a title, a summary, and a link to the full text.</p>\"\n \"published\": \"2024-11-07T12:00:00Z\"\n}\n"},{"location":"fep/b2b8/#long-form-text-with-full-author-information","title":"Long-form text with full author information","text":"{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.com/2024/11/07/long-form-text-author.jsonld\",\n \"name\": \"Long-form text with full author information\",\n \"url\": \"https://example.com/2024/11/07/long-form-text-author.html\",\n \"attributedTo\": {\n \"type\": \"Person\",\n \"id\": \"https://example.com/evan\",\n \"name\": \"Evan Prodromou\",\n \"summary\": \"<p>Founder of Social Web Foundation</p>\",\n \"url\": \"https://example.com/evan\",\n \"icon\": {\n \"type\": \"Image\",\n \"mediaType\": \"image/png\",\n \"url\": \"https://example.com/evan.png\"\n }\n },\n \"summary\": \"<p>This is a long-form text object with full author information. It has a title, a summary, and an URL to the full text.</p>\"\n \"published\": \"2024-11-07T12:00:00Z\"\n}\n"},{"location":"fep/b2b8/#long-form-text-with-embedded-images","title":"Long-form text with embedded images","text":"{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.com/2024/11/07/long-form-text-images.jsonld\",\n \"name\": \"Long-form text with embedded images\",\n \"url\": \"https://example.com/2024/11/07/long-form-text-images.html\",\n \"attributedTo\": \"https://example.com/evan\",\n \"summary\": \"<p>This is a long-form text object with embedded images.</p>\",\n \"content\": \"<p>This is a long-form text object with embedded images.</p><img src=\\\"https://example.com/image1.jpg\\\" alt=\\\"Image 1\\\"><img src=\\\"https://example.com/image2.jpg\\\" alt=\\\"Image 2\\\">\",\n \"attachment\": [\n {\n \"type\": \"Image\",\n \"id\": \"https://example.com/image1.jpg\",\n \"mediaType\": \"image/jpeg\"\n },\n {\n \"type\": \"Image\",\n \"id\": \"https://example.com/image2.jpg\",\n \"mediaType\": \"image/jpeg\"\n }\n ],\n \"published\": \"2024-11-07T12:00:00Z\"\n}\n"},{"location":"fep/b2b8/#long-form-text-with-tags","title":"Long-form text with tags","text":"{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.com/2024/11/07/long-form-text-tags.jsonld\",\n \"name\": \"Long-form text with tags\",\n \"url\": \"https://example.com/2024/11/07/long-form-text-tags.html\",\n \"attributedTo\": \"https://example.com/evan\",\n \"summary\": \"<p>This is a long-form text object with tags.</p>\",\n \"content\": \"<p>@<a href='https://example.com/evan'>evan</a> made this #<a href='https://example.com/tag/example'>example</a>.</p>\",\n \"tag\": [\n {\n \"type\": \"Hashtag\",\n \"name\": \"example\",\n \"href\": \"https://example.com/tag/example\"\n },\n {\n \"type\": \"Mention\",\n \"href\": \"https://example.com/evan\"\n }\n ],\n \"published\": \"2024-11-07T12:00:00Z\"\n}\n"},{"location":"fep/b2b8/#long-form-text-with-context","title":"Long-form text with context","text":"{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.com/2024/11/07/long-form-text-context.jsonld\",\n \"name\": \"Long-form text with context\",\n \"url\": \"https://example.com/2024/11/07/long-form-text-context.html\",\n \"attributedTo\": \"https://example.com/evan\",\n \"summary\": \"<p>This is a long-form text object with context.</p>\",\n \"content\": \"<p>This is a long-form text object with context.</p>\",\n \"context\": [\n \"https://example.com/2024/11/07/series\",\n \"https://example.com/2024/11/07/category\"\n ],\n \"published\": \"2024-11-07T12:00:00Z\"\n}\n"},{"location":"fep/b2b8/#long-form-text-with-preview","title":"Long-form text with preview","text":"{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.com/2025/02/17/long-form-text-preview.jsonld\",\n \"name\": \"Long-form text with preview\",\n \"url\": \"https://example.com/2025/02/17/long-form-text-preview.html\",\n \"attributedTo\": \"https://example.com/evan\",\n \"summary\": \"<p>This is the summary for a long-form text with a preview.</p>\",\n \"content\": \"<p>This is the content for a long-form text with a preview.</p>\",\n \"published\": \"2024-11-07T12:00:00Z\",\n \"image\": {\n \"type\": \"Link\",\n \"href\": \"https://example.com/image.jpg\",\n \"mediaType\": \"image/jpeg\"\n },\n \"preview\": {\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/evan\",\n \"content\": \"<p><strong>Long-form text with preview</strong></p><p>This is the summary for a long-form text with a preview.</p>\",\n \"published\": \"2024-11-07T12:00:00Z\",\n \"attachment\": {\n \"type\": \"Link\",\n \"href\": \"https://example.com/image.jpg\",\n \"mediaType\": \"image/jpeg\"\n }\n }\n}\n"},{"location":"fep/b2b8/#long-form-text-with-senstive-content","title":"Long-form text with senstive content","text":"This article includes a spoiler about the 1941 film Citizen Kane. The sensitive property is set to true, and the dcterms:subject property is used to indicate the topic of the article.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/miscellany\",\n {\"dcterms\": \"http://purl.org/dc/terms/\"}\n ],\n \"id\": \"https://example.com/article/1\",\n \"type\": \"Article\",\n \"name\": \"Spoiler for Citizen Kane\",\n \"summary\": \"<p>I am going to tell you what Rosebud was.</p>\",\n \"sensitive\": true,\n \"dcterms:subject\": [\"Citizen Kane\"],\n \"contents\": \"<p>Rosebud was his sled!</p>\",\n \"tag\": {\n \"id\": \"https://example.com/tag/citizenkane\",\n \"name\": \"citizenkane\",\n \"type\": \"Hashtag\"\n }\n}\n"},{"location":"fep/b2b8/#user-interface-guidance","title":"User interface guidance","text":"Consumers should use their native interfaces to handle Article objects in an intuitive way that integrates well with other object types. The following illustrations provide examples of how Article objects might be displayed in a stream-oriented social web interface, such as a microblogging application. The UI elements are labelled with the properties of the Article object that most likely correspond to them.
An example of a long-form text object with an image property displayed in a social stream.
An example of a long-form text object without an image property displayed in a social stream.
An example of a long-form text object without a name property displayed in a social stream.
An example of a long-form text object with a sensitive property displayed in a social stream with a content warning.
An example of a long-form text object with a preview property displayed in a social stream. Note that the consumer is responsible for displaying a link to the article's full content.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/bad1/","title":"Index","text":"hide: - navigation description: \"[AS2-Core] provides examples 18, 19, 32 which represent the \"history\" of an object. Particularly in example 32, we see an object being Created, Updated, and Deleted. However, there is no property dedicated to advertising a collection fit for this purpose. This FEP attempts to define one.\"
"},{"location":"fep/bad1/#fep-bad1-object-history-collection","title":"FEP-bad1: Object history collection","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT 2023-06-15 #124 - codeberg"},{"location":"fep/bad1/#summary","title":"Summary","text":"[AS2-Core] provides examples 18, 19, 32 which represent the \"history\" of an object.
Particularly in example 32, we see an object being Created, Updated, and Deleted. However, there is no property dedicated to advertising a collection fit for this purpose. This FEP attempts to define one.
"},{"location":"fep/bad1/#examples-taken-from-activitystreams-20","title":"Examples taken from ActivityStreams 2.0","text":"(This section is non-normative.)
For convenience, the following examples are reproduced from the ActivityStreams 2.0 [AS2-Core] specification. Examples 18 and 19 are found in normative sections; example 32 is found in a non-normative section.
Example 18:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"summary\": \"Object history\",\n \"type\": \"Collection\",\n \"totalItems\": 2,\n \"items\": [\n {\n \"type\": \"Create\",\n \"actor\": \"http://www.test.example/sally\",\n \"object\": \"http://example.org/foo\"\n },\n {\n \"type\": \"Like\",\n \"actor\": \"http://www.test.example/joe\",\n \"object\": \"http://example.org/foo\"\n }\n ]\n}\n Example 19:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"summary\": \"Object history\",\n \"type\": \"OrderedCollection\",\n \"totalItems\": 2,\n \"orderedItems\": [\n {\n \"type\": \"Create\",\n \"actor\": \"http://www.test.example/sally\",\n \"object\": \"http://example.org/foo\"\n },\n {\n \"type\": \"Like\",\n \"actor\": \"http://www.test.example/joe\",\n \"object\": \"http://example.org/foo\"\n }\n ]\n}\n Example 32:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"oa\": \"http://www.w3.org/ns/oa#\",\n \"prov\": \"http://www.w3.org/ns/prov#\",\n \"dcterms\": \"http://purl.org/dc/terms/\",\n \"dcterms:created\": {\n \"@id\": \"dcterms:created\",\n \"@type\": \"xsd:dateTime\"\n }\n }\n ],\n \"summary\": \"Editing history of a note\",\n \"type\": \"Collection\",\n \"items\": [\n {\n \"id\": \"http://example.org/activity/20150101000000\",\n \"type\": [ \"Create\", \"prov:Activity\" ],\n \"actor\": {\n \"id\": \"http://example.org/#eric\",\n \"name\": \"Eric\"\n },\n \"summary\": \"Eric wrote a note.\",\n \"object\": {\n \"id\": \"http://example.org/entry/20150101000000\",\n \"type\": [ \"Note\", \"prov:Entity\" ],\n \"attributedTo\": \"http://example.org/#eric\",\n \"content\": \"Remember... all I'm offering is the trooth. Nothing more.\"\n },\n \"published\": \"2015-01-01T00:00:00Z\"\n },\n {\n \"id\": \"http://example.org/activity/20150101000059\",\n \"type\": [ \"Update\", \"prov:Activity\", \"oa:Annotation\" ],\n \"summary\": \"Eric edited a note.\",\n \"dcterms:created\": \"2015-01-01T00:00:59Z\",\n \"dcterms:creator\": { \"@id\": \"http://example.org/#eric\" },\n \"oa:hasBody\": {\n \"id\": \"http://example.org/entry/20150101000059\",\n \"type\": [ \"Note\", \"prov:Entity\" ],\n \"content\": \"Remember... all I'm offering is the truth. Nothing more.\",\n \"prov:wasAttributedTo\": { \"@id\": \"http://example.org/#eric\" },\n \"prov:wasRevisionOf\": { \"@id\": \"http://example.org/entry/20150101000000\" }\n },\n \"oa:hasTarget\": { \"@id\": \"http://example.org/entry/20150101000000\" },\n \"oa:motivatedBy\": { \"@id\": \"oa:editing\" },\n \"prov:generated\": { \"@id\": \"http://example.org/entry/20150101000059\" },\n \"prov:wasInformedBy\": { \"@id\": \"http://example.org/activity/20150101000000\" }\n },\n {\n \"id\": \"http://example.org/activity/20150101010101\",\n \"type\": [ \"Delete\", \"prov:Activity\" ],\n \"actor\": \"http://example.org/#eric\",\n \"summary\": \"Eric deleted a note.\",\n \"object\": \"http://example.org/entry/20150101000059\",\n \"published\": \"2015-01-01T01:01:01Z\"\n }\n ]\n}\n From these examples, Example 18 is nearly identical to Example 19, except for the type being Collection in Example 18, and OrderedCollection in Example 19. An object's history collection will necessarily be ordered chronologically, although whether the ordering should be forward chronological or reverse chronological is an open question; at the time of writing this FEP, [ActivityPub] Section 5 currently contains the following language:
An OrderedCollection MUST be presented consistently in reverse chronological order.
This language indicates that if OrderedCollection is used, the ordering MUST be reverse chronological.
history special collection","text":"An object's history is discovered through the history property of an object. The history MUST be an OrderedCollection.
The history stream contains all activities which target the object as object, where the actor matches the attributedTo actor. This might include Create, Update, and/or Delete activities.
This is differentiated from context, which per FEP-7888 may be a collection containing related objects and activities. It is possible in certain cases to obtain an object's history by filtering such a collection for all items that contain an object referencing a given object, but this is not a consistent or straightforward way of doing so.
In the associated context, we define the following terms:
"},{"location":"fep/bad1/#history","title":"history","text":"URI : https://w3id.org/fep/bad1/history
Notes : A collection containing all activities performed by the author related to this object's representation.
JSON-LD definition (@id pending adoption of FEP-9606):
{\n \"@context\": {\n \"history\": {\n \"@id\": \"https://w3id.org/fep/bad1/history\",\n \"@type\": \"@id\"\n }\n }\n}\n Example:
{\n \"@context\": [\n \"https://w3id.org/fep/bad1/history\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"id\": \"https://example.com/some-file\",\n \"type\": \"Tombstone\",\n \"formerType\": \"Document\",\n \"url\": \"https://example.com/404\",\n \"history\": {\n \"id\": \"https://example.com/some-object/log\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n {\n \"id\": \"https://example.com/some-file/log/3\",\n \"type\": \"Delete\",\n \"object\": \"https://example.com/some-file\"\n },\n {\n \"id\": \"https://example.com/some-file/log/2\",\n \"type\": \"Update\",\n \"object\": {\n \"id\": \"https://example.com/some-file\",\n \"url\": \"https://example.com/storage/hash2\"\n }\n },\n {\n \"id\": \"https://example.com/some-file/log/1\",\n \"type\": \"Create\",\n \"object\": {\n \"type\": \"Document\",\n \"url\": \"https://example.com/storage/hash1\"\n }\n }\n ]\n }\n}\n"},{"location":"fep/bad1/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c0e0/","title":"FEP-c0e0: Emoji reactions","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2024-08-08 #384 Discussions codeberg"},{"location":"fep/c0e0/#summary","title":"Summary","text":"This document describes how emoji reactions are implemented in ActivityPub network.
"},{"location":"fep/c0e0/#history","title":"History","text":"Misskey supports emoji reactions since version 10.97.0 (2019). Pleroma supports emoji reactions since version 2.0.0 (2020).
"},{"location":"fep/c0e0/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/c0e0/#emojireact-activity","title":"EmojiReact activity","text":"EmojiReact activity type is considered to be a part of LitePub vocabulary. Its full IRI is http://litepub.social/ns#EmojiReact.
This activity is similar to Like activity. In addition to standard properties of Like activity, EmojiReact activity MUST have a content property. Reaction content MUST be either a single unicode grapheme, or a name of a custom emoji enclosed in colons (shortcode).
If custom emoji is used, EmojiReact activity MUST have a tag property containing a single Emoji object. The value of its name property MUST contain the name of the custom emoji, and it SHOULD be enclosed in colons. The embedded Emoji can originate from a server that is different from the actor's server.
An actor can generate multiple EmojiReact activities for a single object. However, implementers MAY choose to not allow more than one reaction with the same emoji, or more than one reaction per object.
Example with unicode emoji:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"litepub\": \"http://litepub.social/ns#\",\n \"EmojiReact\": \"litepub:EmojiReact\"\n }\n ],\n \"actor\": \"https://alice.social/users/alice\",\n \"content\": \"\ud83d\udd25\",\n \"id\": \"https://alice.social/activities/65379d47-b7aa-4ef6-8e4f-41149dda1d2c\",\n \"object\": \"https://bob.social/objects/57caeb99-424c-4692-b74f-0a6682050932\",\n \"to\": [\n \"https://alice.social/users/alice/followers\",\n \"https://bob.social/users/bob\"\n ],\n \"type\": \"EmojiReact\"\n}\n Example with custom emoji:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\": \"http://joinmastodon.org/ns#\",\n \"Emoji\": \"toot:Emoji\",\n \"litepub\": \"http://litepub.social/ns#\",\n \"EmojiReact\": \"litepub:EmojiReact\"\n }\n ],\n \"actor\": \"https://alice.social/users/alice\",\n \"content\": \":blobwtfnotlikethis:\",\n \"id\": \"https://alice.social/activities/65379d47-b7aa-4ef6-8e4f-41149dda1d2c\",\n \"object\": \"https://bob.social/objects/57caeb99-424c-4692-b74f-0a6682050932\",\n \"tag\": [\n {\n \"icon\": {\n \"mediaType\": \"image/png\",\n \"type\": \"Image\",\n \"url\": \"https://alice.social/files/1b0510f2-1fb4-43f5-a399-10053bbd8f0f\"\n },\n \"id\": \"https://alice.social/emojis/blobwtfnotlikethis\",\n \"name\": \":blobwtfnotlikethis:\",\n \"type\": \"Emoji\",\n \"updated\": \"2024-02-07T02:21:46.497Z\"\n }\n ],\n \"to\": [\n \"https://alice.social/users/alice/followers\",\n \"https://bob.social/users/bob\"\n ],\n \"type\": \"EmojiReact\"\n}\n"},{"location":"fep/c0e0/#like-with-content","title":"Like with content","text":"Emoji reaction can also be represented as a Like activity. This variant of emoji reaction will processed by non-supporting implementations as a regular \"like\", and when that is preferable, implementers MAY use Like type instead of EmojiReact type.
Implementations MUST process Like with content in the same way as EmojiReact activities.
Emoji reactions can be retracted using a standard Undo activity:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"actor\": \"https://alice.social/users/alice\",\n \"id\": \"https://alice.social/activities/99b8f47b-f3a9-4cf5-94a2-95352e7462d6\",\n \"object\": \"https://alice.social/activities/65379d47-b7aa-4ef6-8e4f-41149dda1d2c\",\n \"to\": [\n \"https://alice.social/users/alice/followers\",\n \"https://bob.social/users/bob\"\n ],\n \"type\": \"Undo\"\n}\n"},{"location":"fep/c0e0/#emojireactions-collection","title":"emojiReactions collection","text":"The list of emoji reactions to an object can be exposed using the emojiReactions property. Its full IRI is http://fedibird.com/ns#emojiReactions.
The URL specified by this property MUST resolve to a collection containing Like (with content) and EmojiReact activities.
This document is based on implementations of emoji reactions in Misskey, Pleroma and Fedibird.
A number of other projects announced support for this FEP after it was published:
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c118/","title":"FEP-c118: Content licensing support","text":"Authors Status Date received Tracking issue Discussions Repository Tim Bray tbray@texgtuality.comDRAFT 2023-01-16 #57 - codeberg"},{"location":"fep/c118/#summary","title":"Summary","text":"Currently, popular Fediverse software does very little to establish the legal status of posts. Controversy over indexing and scraping the Fediverse is common. The hope is that providing a legal framework to express the desires of users as to how their content may be re-used might bring order to this debate.
"},{"location":"fep/c118/#scenario","title":"Scenario","text":"Dramatis Personae: * A somewhat-technical new Fediverse arrival, hereinafter Noob * Existing Fediverse Voices, hereinafer EFV1, EFV2, etc
Noob: Hey, I want to crawl the Fediverse and build an index so we can\u2026
EFV1: Stop! Go back! Don't!
EFV2: We prefer not to do that here because we want to avoid into big-tech surveillance capitalism.
Noob: But I mean they're all just URLs, right?
EFV3: Burn the witch!
EFV4: Unclean!
Noob: Oh, I guess you don't want me to do this?
EFV5: Never!
Noob: Shouldn't you post the rules somewhere? I mean, lots of people are already doing this.
EFV6: Defederate his instance!
"},{"location":"fep/c118/#discussion","title":"Discussion","text":"While Fediverse search has become less controversial since the previous revision of this FEP, the issue remains highly relevant with the advent of crawlers from AI companies gathering material to feed into LLMs.
At the moment, the Fediverse privacy story is unsatisfactory. Unless a user really understands the visibility levels, every post they make instantly becomes part of the permanent indelible Internet memory. There are no technical and very few legal barriers to prevent anyone from doing anything with retrieved post content.
In developed societies, when there are behaviors that are logistically possible but society would like to regulate, legal tools are frequently applied. Examples would be dangerous driving and public smoking.
A content-licensing framework for the Fediverse could include some or all of the following:
I note that this document template comes with a carefully-considered Creative Commons license. Do not the contributions of the humans who are enriching the Fediverse deserve a comparable level of protection?
"},{"location":"fep/c118/#caveat","title":"Caveat","text":"Note that establishing a legal content-licensing framework will not prevent certain bad actors from scraping the Fediverse and mis-using the harvested data. That's OK because it will establish a legal framework that will present a significant barrier to commercial data harvesters, and provide a tool to combat certain classes of mis-use.
"},{"location":"fep/c118/#see-also","title":"See also:","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c16b/","title":"FEP-c16b: Formatting MFM functions","text":"Authors Status Date received Tracking issue Discussions Repository ilja ilja@ilja.spaceDRAFT 2024-08-10 #383 Discussions codeberg"},{"location":"fep/c16b/#summary","title":"Summary","text":"This FEP recommends a method for formatting MFM in ActivityPub post content using HTML with custom classes and [data-* attributes]. Furthermore, this FEP provides a new extension term to indicate that this HTML representation is used.
"},{"location":"fep/c16b/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in [RFC-2119]. \u201cFediverse implementation\u201d or \"implementation\u201d is to be interpreted as an ActivityPub conformant Client, ActivityPub conformant Server or ActivityPub conformant Federated Server as described in [ActivityPub].
"},{"location":"fep/c16b/#acknowledgements","title":"Acknowledgements","text":"(This section is non-normative.)
The core idea behind this FEP is attributed to Johan150 on the Foundkey issue tracker[1]. Specifically, the proposal for representing the MFM functions in HTML using span elements with custom classes and data-* attributes.
(This section is non-normative.)
It is common for a Fediverse implementation to allow a markup language as input for text. Federation of this content generally happens by converting this text input to a proper HTML representation that another implementation can easily understand. This HTML representation is federated over ActivityPub using the content property of the [ActivityStreams] Object. Meanwhile, the source property, which was added by ActivityPub, can optionally be used to provide the original input and input format.
Misskey has been using its own [Markup language For Misskey], also known as MFM. MFM is mostly composed of a combination of HTML, Markdown, Katex, and custom MFM functions of the form $[name content]. Properly displaying what these MFM functions intent, generally requires complex CSS or even Javascript. As such, only a watered down HTML representation is provided in the content. This representation can strip out so much information that a receiving implementation cannot always properly display what the author meant to convey. The only option for a receiving implementation who wants to display the MFM correctly, is to re-parse the content of the source property when it's mediaType has value text/x.misskeymarkdown. This causes not only unnecessary overhead, but also compatibility issues, especially when two implementations are using a different parser.
(This section is non-normative.)
An MFM Function consists of a name, optionally one or more attributes who may or may not have a value, and a content. It has the form $[name.attribute1,attribute2=value content].
(This section is non-normative.)
$[x2 Misskey expands the world of the Fediverse]\n$[jelly.speed=2s Misskey expands the world of the Fediverse]\n$[spin.x,speed=0.5s Misskey expands the world of the Fediverse]\n"},{"location":"fep/c16b/#html-representation-of-mfm-functions","title":"HTML representation of MFM functions","text":"When representing the MFM function in HTML, a span element MUST be used. The span element MUST have a class mfm-name where name is the name of the MFM function. When the MFM function has attributes, the span element MUST have a data-* attribute data-mfm-attributename for each attribute, where attributename is the name of the attribute in question. If the attribute of the MFM function has a value, the data-* attribute MUST have the same value.
(This section is non-normative.)
This turns the previous examples into
<span class=\"mfm-x2\">Misskey expands the world of the Fediverse</span>\n<span class=\"mfm-jelly\" data-mfm-speed=\"2s\">Misskey expands the world of the Fediverse</span>\n<span class=\"mfm-flip\" data-mfm-x data-mfm-speed=\"0.5s\">Misskey expands the world of the Fediverse</span>\n"},{"location":"fep/c16b/#other-mfm-components","title":"Other MFM components","text":"While this FEP focuses on the representation of MFM functions, MFM consist of more than just these MFM functions. The HTML representation in the content property MUST be correct and complete such that a receiving implementation can use it to correctly display what the MFM conveys.
HTML and Markdown are generally expressed correctly in the content property, and are both widely used on the Fediverse. Therefor these are not considered problematic in the same sense as the MFM functions are.
Katex suffers the same problem of generally not being expressed properly in the content property. For properly expressing the Katex input as HTML, [FEP-dc88] SHOULD be used.
(This section is non-normative.)
When compatibility with an MFM capable, but FEP-c16b non-compliant, implementation is desired, the source may still need to be federated using \"mediaType\": \"text/x.misskeymarkdown\". Meanwhile, the incoming source from this implementation may still need to be re-parsed. As such, a discovery mechanism is required to signal to a FEP-c16b compliant implementation that the content may be used directly.
For this purpose, a new extension term is proposed, as described in [FEP-888d].
"},{"location":"fep/c16b/#htmlmfm","title":"htmlMfm","text":"To signal that the content is FEP-c16b compliant, the implementation MAY use the extension term htmlMfm with value true. When the content is not FEP-c16b compliant, the implementation MUST NOT use the extension term htmlMfm with value true, but MAY use the extension term htmlMfm with value false.
content is FEP-c16b compliant.https://w3id.org/fep/c16b#htmlMfmhttps://www.w3.org/ns/activitystreams#Object(This section is non-normative.)
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"htmlMfm\": \"https://w3id.org/fep/c16b#htmlMfm\"\n }\n ],\n \"content\": \"<span class=\\\"mfm-spin\\\" data-mfm-x data-mfm-speed=\\\"0.5s\\\">Misskey expands the world of the Fediverse</span>\",\n \"source\": {\n \"content\": \"$[spin.x,speed=0.5s Misskey expands the world of the Fediverse]\",\n \"mediaType\": \"text/x.misskeymarkdown\"\n },\n \"htmlMfm\": true\n}\n"},{"location":"fep/c16b/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c180/","title":"FEP-c180: Problem Details for ActivityPub","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.orgDRAFT 2025-03-11 #531 Discussions codeberg"},{"location":"fep/c180/#summary","title":"Summary","text":"ActivityPub is a RESTful API and HTTP-based protocol for standards-based social networking, but does not specify an error format. This document provides a profile of the Problem Details for HTTP APIs specification (RFC 9457) for use with ActivityPub.
"},{"location":"fep/c180/#introduction","title":"Introduction","text":"ActivityPub is the W3C standard for federated social networking. It describes a standard RESTful API for social applications that allows people to create and share social content like text, images, audio and video, as well as reacting to social content and building a social graph of connections between people. ActivityPub also includes a standard protocol for federating social content between servers, so that people on different social platforms can interact with each other.
Both the client API and the server-to-server protocol are based on HTTP, and use HTTP status codes to indicate the success or failure of requests. However, HTTP status codes are not always sufficient to describe the nature of an error, or to provide enough information for a client to recover from an error.
The Problem Details for HTTP APIs specification (RFC 9457) describes a way to provide more detailed information about errors in an HTTP response. The format includes a machine-readable description of the error, as well as a human-readable explanation, additional data about the error, and a link to more information about the error.
This document describes a number of specific error types that are relevant to ActivityPub, and provides guidance on how to use the Problem Details for HTTP APIs format with ActivityPub.
"},{"location":"fep/c180/#motivating-use-cases","title":"Motivating use cases","text":"ActivityPub servers SHOULD use the Problem Details for HTTP APIs format to describe errors in responses to HTTP requests. The format is described in RFC 9457.
These types of HTTP request in the ActivityPub API and federation protocol SHOULD use the Problem Details format for errors (abbreviations used in this document are in parentheses):
Other ActivityPub requests MAY use the Problem Details format.
The about:blank type defined in RFC 9457 MAY be used for problems that do not have a specific type. Other types registered in the IANA Problem Type Registry MAY be used for specific problems.
Problem types in this vocabulary use the https://w3id.org/fep/c180 prefix.
Each of the following problem types lists the applicability of the problem (per the list of request types above), the type URI, the title of the problem, the HTTP status code that SHOULD be used, and additional fields that MAY be included in the response.
"},{"location":"fep/c180/#unsupported-type","title":"Unsupported type","text":"id of the object with the unsupported typetype that is not supportedThis indicates that the type of the activity, or one of the objects referred to by the activity, is not supported by the API server or the receiving federation protocol server.
"},{"location":"fep/c180/#object-does-not-exist","title":"Object does not exist","text":"id of the object that does not existThe activity refers to an object in one of its properties, such as object, target, or an addressing property, but the object does not exist. Recursively connected objects, like the inReplyTo property of the object property, can also be checked.
Note that this type is distinct from an endpoint returning a 404 Not Found status code for a GET request for an object that does not exist, or for posting to an endpoint that does not exist.
"},{"location":"fep/c180/#duplicate-delivery","title":"Duplicate delivery","text":"id of the activity that was previously deliveredThe activity has already been delivered to the inbox or to all accounts using the sharedInbox.
Note that this is different from Redundant activity. Duplicate delivery is when the same activity is delivered multiple times. Redundant activity is when two different activities that do the same thing are received.
"},{"location":"fep/c180/#redundant-activity","title":"Redundant activity","text":"id of the previous activityThe activity is a duplicate of a previous activity which has already been processed by the server, and which has not been reverted with an Undo activity or with other activities. The duplicate property contains the id of the previous activity.
Activity types that are often treated as idempotent and can only be processed once include Create, Delete, Follow, Accept, Reject, Add, Remove, Block, Undo, and Like. Other activity types like Announce are treated as idempotent by some servers.
Note that this is different from Duplicate delivery.Redundant activity is when two different activities that do the same thing are received. Duplicate delivery is when the same activity is delivered multiple times.
"},{"location":"fep/c180/#approval-required","title":"Approval required","text":"id of the actor who must approve the activityThe activity will be delivered to the addressees, but may not have side effects applied until it is approved by an administrator, moderator, or one of the addressees.
For example, a Follow activity may be delivered to the addressee's inbox, but the Accept activity may not be returned until the addressee approves the follow request.
As another example, a Create activity with an object property with an inReplyTo property may require approval by the author of the replied-to object before it is added to that object's replies collection.
This problem type would be used for activities that are manually approved, not automatically approved.
"},{"location":"fep/c180/#not-an-actor","title":"Not an actor","text":"id of the object that is not an actorThe activity refers to an object in one of its properties, such as object or an addressing property, that requires an ActivityPub actor to be correctly processed, but the object is not an actor.
id of the principalid of the actorThe security principal of the request, such as the authenticated user, does not match the actor that is the subject of the activity.
For example, the authenticated user is trying to send a Follow activity to another actor, but the actor property of the activity is not the authenticated user.
Note that it is possibly valid for the actor property of an activity to not be the same as the authenticated user; for example, with inbox forwarding.
id of the actorid of the resource the actor is unauthorized to accessThe actor is not authorized to perform the given activity on, to, or from a given object.
For example, with an Add activity, the actor is not authorized to add the object to the target collection.
As another example, with a Delete activity, the actor is not authorized to delete the object.
Another example would be a Like activity for an object where the actor has been blocked by the creator of the object.
Note that this type is distinct from Principal not authorized, which indicates that the authenticated user is not authorized to perform the activity.
"},{"location":"fep/c180/#principal-not-authorized","title":"Principal not authorized","text":"id of the principalid of the resource the principal is unauthorized to accessThis problem type indicates that the security principal, such as the authenticated user, is not authorized to perform the given activity on, to, or from a given object.
It can also be used to indicate that the authenticated user is not authorized to GET an object, either directly or through a proxy.
This type is distinct from actor not authorized. This type should only be used when the principal and the actor are distinct, or when there is no actor (such as with GET requests).
"},{"location":"fep/c180/#client-not-authorized","title":"Client not authorized","text":"id of the clientThis problem type is applicability GET and POST requests.
This indicates that the client is not authorized to perform the given activity. The security principal, like the authenticated user, may be authorized, but the client is not.
An example would be a client that uses OAuth 2.0 to authenticate, perhaps with FEP-d8c2, but has not been granted the proper scopes to perform the activity.
This error type implies, but does not promise, that the security principal would be authorized to perform the activity with a different client.
This problem type is primarily for the ActivityPub API, between a client and a server. In the case of the federation protocol, where the principal is closely tied to the platform that is sending the activity, there may not be a meaningful way for the principal to interact without the client (in this case, their server).
"},{"location":"fep/c180/#unsupported-media-type","title":"Unsupported media type","text":"The media type of the uploaded file is not supported by the server.
"},{"location":"fep/c180/#media-too-large","title":"Media too large","text":"The uploaded file is too large to be processed by the server.
"},{"location":"fep/c180/#no-applicable-addressees","title":"No applicable addressees","text":"The activity does not have any addressees that are applicable to the server. This could be because the activity has no to, cc, or bcc properties, or because the addressees do not have inboxes on the server.
Another case is where the addressees are Collections, and no actor in the collection has an inbox on the server. For example, if an activity is addressed to the actor's followers collection, but none of the followers have inboxes on the server.
"},{"location":"fep/c180/#rate-limit-exceeded","title":"Rate limit exceeded","text":"The client or the security principal has exceeded the rate limit for the given activity. The server MAY include a Retry-After header in the response to indicate when the rate limit will be reset.
This problem type is primarily applicable to the ActivityPub API, between a client and a server. It is unusual for a server to rate limit incoming activities over the federation protocol.
"},{"location":"fep/c180/#privacy-considerations","title":"Privacy considerations","text":"Some of the problem types in this document may reveal information about the server's internal state, such as the existence of an object, the relationship of an object to an actor, or a relationship between actors. Servers should be careful to avoid revealing sensitive information in error messages.
"},{"location":"fep/c180/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c390/","title":"FEP-c390: Identity Proofs","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2022-11-23 #34 Discussions codeberg"},{"location":"fep/c390/#summary","title":"Summary","text":"This proposal describes a mechanism of creating verifiable links between Decentralized Identifiers and ActivityPub actor profiles.
Potential applications include: identity verification, end-to-end encryption and account migrations.
"},{"location":"fep/c390/#history","title":"History","text":"Identity proof is a JSON document that represents a verifiable bi-directional link between a Decentralized Identifier and an ActivityPub actor.
It MUST contain the following properties:
type (REQUIRED): the value of type property MUST be the string VerifiableIdentityStatement.subject (REQUIRED): the decentralized identifier (DID) that represents a cryptographic key belonging to an actor.alsoKnownAs (REQUIRED): the value of this property MUST match the actor ID.proof (REQUIRED): the data integrity proof, as defined by Data Integrity specification.The document MAY contain additional properties.
Identity proofs SHOULD be attached to an actor object, under the attachment property.
The identity proof document MUST contain a data integrity proof, which includes a cryptographic proof and parameters required to verify it. It MUST be created according to the Data Integrity specification, section 4.2 Add Proof. The value of verificationMethod property of the data integrity proof MUST match the value of subject property of the identity proof document.
The resulting data integrity proof MUST be added to identity proof document under the proof key.
Example of an actor object linked to a did:key identifier:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/did/v1\",\n \"https://w3id.org/security/data-integrity/v1\",\n \"https://w3id.org/fep/c390\"\n ],\n \"type\": \"Person\",\n \"id\": \"https://server.example/users/alice\",\n \"inbox\": \"https://server.example/users/alice/inbox\",\n \"outbox\": \"https://server.example/users/alice/outbox\",\n \"attachment\": [\n {\n \"type\": \"VerifiableIdentityStatement\",\n \"subject\": \"did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\",\n \"alsoKnownAs\": \"https://server.example/users/alice\",\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"created\": \"2023-02-24T23:36:38Z\",\n \"verificationMethod\": \"did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\"\n }\n }\n ]\n}\n WARNING: The example above uses eddsa-jcs-2022 cryptosuite, which doesn't have stable specification.
"},{"location":"fep/c390/#proof-verification","title":"Proof verification","text":"The consuming implementations MUST check the authenticity of identity proof document by verifying its data integrity proof. If the proof can not be verified, or if the value of verificationMethod property of the data integrity proof doesn't match the value of subject property of the identity proof, or if the value of alsoKnownAs property of the identity proof doesn't match the actor ID, the identity proof MUST be discarded.
Verification process MUST follow the Data Integrity specification, section 4.4 Verify Proof.
"},{"location":"fep/c390/#linking-identities","title":"Linking identities","text":"The consuming implementations SHOULD treat identities denoted by subject and alsoKnownAs properties of identity proof as belonging to the same entity.
If two actors have identity proofs with the same subject property, they SHOULD be treated as different identities of the same entity.
Servers MUST present identity proofs to clients in their original form. Clients MAY perform independent verification of identity proofs if needed.
"},{"location":"fep/c390/#test-vectors","title":"Test vectors","text":"See fep-c390.feature
"},{"location":"fep/c390/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c4ad/","title":"Index","text":"hide: - navigation description: \"This document proposes a standard for managing viewership history across the Fediverse. It addresses the common issue of posts being repeatedly shown to users on different clients. The goal is to enable servers to track which posts have been viewed by individual users and ensure that clients do not display these posts again. This proposal aims to enhance user experience by preventing the redundant display of already seen posts, commonly requested as \"Hide already seen posts\" or \"stop repeating already seen posts\".\"
"},{"location":"fep/c4ad/#fep-c4ad-viewership-history","title":"FEP-c4ad: Viewership History","text":"Authors Status Date received Tracking issue Discussions Repository AvidSeeker avidseeker7@protonmail.comDRAFT 2024-07-15 #367 Discussions codeberg"},{"location":"fep/c4ad/#summary","title":"Summary","text":"This document proposes a standard for managing viewership history across the Fediverse. It addresses the common issue of posts being repeatedly shown to users on different clients. The goal is to enable servers to track which posts have been viewed by individual users and ensure that clients do not display these posts again. This proposal aims to enhance user experience by preventing the redundant display of already seen posts, commonly requested as \"Hide already seen posts\" or \"stop repeating already seen posts\".
"},{"location":"fep/c4ad/#creating-a-viewership-record","title":"Creating a Viewership Record","text":"When a user views a post, the server should create a record of this event. This record will include the following properties:
id (REQUIRED): the unique identifier of the viewership record.type (REQUIRED): the type of the object MUST be ViewershipRecord.actor (REQUIRED): the actor who viewed the post.object (REQUIRED): the unique identifier of the post that was viewed.published (RECOMMENDED): the date and time at which the post was viewed.Clients should query the server for viewership records to determine whether a post has been viewed by the user. The server should provide an endpoint for clients to fetch viewership records for a specific user.
"},{"location":"fep/c4ad/#handling-viewed-posts","title":"Handling Viewed Posts","text":"When fetching posts, clients must filter out posts that have been viewed by the user based on the viewership records. This ensures that users are not shown the same posts repeatedly.
"},{"location":"fep/c4ad/#copyright","title":"Copyright","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c551/","title":"FEP-c551: Use ECMAScript Modules to Create Conformance Tests for Fediverse Enhancement Proposals","text":"Authors Status Date received Tracking issue Discussions Repository bengo ben@bengo.coDRAFT 2024-07-11 #366 - codeberg"},{"location":"fep/c551/#summary","title":"Summary","text":"This is a proposal to enhance the fediverse by creating test cases for FEPs as ECMAScript Modules.
"},{"location":"fep/c551/#contents","title":"Contents","text":"FEP-d9ad proposes to Create Conformance Tests for Fediverse Enhancement Proposals, and specifies components that all Conformance Tests may use and describe in their Test Specifications. It does not specify a format for implementing FEP-d9ad Conformance Tests in any programming language.
This FEP-c551 proposes to supplement human-readable FEP-d9ad Conformance Tests with implementations of the test as Test Objects exported from Test Modules. Each Test Object has a run function parameterized by a [Test Input] and returning a Promise of a Test Result.
When a tester comes up with a new test for a FEP, they create a human-readable Test Specification describing how to test whether some subject conforms to the FEP.
ECMAScript developers implement Test Specifications as automatable code by using ECMAScript to create Test Functions that execute the test logic and Test Objects that group the Test Function with more info like the test's name, required input, and possible outcomes. Test Objects are distributed in ECMAScript Modules published on the web, e.g. in .js or .mjs files.
Testers invoke the Test Function once for each Test Input, await any returned Promises, and receive a Test Result describing the outcome of running the test.
Test Specifications are human-readable documents that specify the behavior of a test.
Test Specifications SHOULD include Conformance Test Component specifications from FEP-d9ad.
An example of a test specification is fep-521a-test-case.md.
"},{"location":"fep/c551/#test-modules","title":"Test Modules","text":"FEP testers MAY publish implementations of their test specifications as an ECMAScript Module following the recommendations in this proposal. Such modules may be referred to as Test Modules.
Test Modules SHOULD have no imports. This is to ensure portability of the test modules.
Test Modules MAY export a default export object that is a Test Object
Test Modules SHOULD be resilient to being parsed and evaluated in various ECMAScript runtimes (e.g. node.js or a web browser like Firefox).
An example of a test module can be found in activitypub-testing-fep-521a.
"},{"location":"fep/c551/#example-test-module","title":"Example Test Module","text":"export default {\n name: 'invalid script module name',\n run: (input) => ({ outcome: 'passed' }),\n type: ['https://w3id.org/fep/c551#Test'],\n '@context': [\"https://www.w3.org/ns/activitystreams\"],\n}\n"},{"location":"fep/c551/#test-objects","title":"Test Objects","text":"Test Objects are ECMAScript Objects that represent a named, runnable test, e.g. a test specified by a FEP-d9ad Conformance Test.
Test Objects MUST have a property named type whose value is either the string https://w3id.org/fep/c551#Test or an Array containing that string.
Test Objects MUST have a property named name whose value is a string.
Test Objects SHOULD have a property named @context whose value is an Array containing https://www.w3.org/ns/activitystreams.
An example of a test object is exported in activitypub-testing-fep-521a.
Test Objects SHOULD have a property named run whose value is a Test Function].
Test Object run calls SHOULD return a Promise that resolves to a Test Result.
Test Object run functions SHOULD be resilient to being run in various ECMAScript runtimes (e.g. node.js or a web browser like Firefox).
A Test Input is the first parameter to a test's run function.
Test Input MUST be an object. A test with several logically distinct inputs should give each input a name, and add each named input as a property within a top-level input object.
Test Input values SHOULD conform to the specification of the called test's Input spec.
"},{"location":"fep/c551/#test-results","title":"Test Results","text":"Test Results MUST have a property named outcome whose value is a string.
Test Results SHOULD have a property named info whose value is a string.
Test Results MAY have a property named pointer that contextualizes the outcome, e.g. an object with a property for each value that led to the outcome. For example, if a test outcome is failed because some number was too low, you can set the result info to \"number too low\" and pointer to { number: 100 }.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c551/tests/","title":"fep-c551-tests","text":"A test suite that tests for conformance to FEP-c551: Use ECMAScript Modules to Create Conformance Tests for Fediverse Enhancement Proposals.
"},{"location":"fep/c551/tests/#tests","title":"Tests","text":""},{"location":"fep/c551/tests/#fep-c551-module-must-export-test-object","title":"fep-c551 module must export test object","text":"fep-c551-module-must-export-test-objectactivitypub-testing run test \\\n--url=\"$(data-url ./fep-c551-module-must-export-test-object.js)\" \\\n--input.module=\"$(cat <<EOF\nexport default {\n name: 'sample test',\n run: () => {\n return { outcome: 'inapplicable' }\n }\n}\nEOF\n)\"\n The data-url command is provided by the following shell function:
data-url() {\n if [ -z \"$1\" ]; then\n echo \"usage: data-url file\" >&2\n exit 1\n fi\n mimetype=$(file -bN --mime-type \"$1\")\n content=$(base64 < \"$1\")\n echo \"data:$mimetype;base64,$content\"\n}\n"},{"location":"fep/c5a1/","title":"FEP-c5a1: To-do's","text":"Authors Status Date received Tracking issue Discussions Repository Lynn Foster lynn@mikorizal.org DRAFT 2024-01-31 #256 - codeberg"},{"location":"fep/c5a1/#summary","title":"Summary","text":"This document describes an implementation of simple to-do's or tasks based on ActivityPub protocol and Valueflows vocabulary. A to-do is a simple work commitment, and can be created for oneself or another person. Optionally, when the to-do is done, that can be recorded also.
"},{"location":"fep/c5a1/#history","title":"History","text":"This is a parallel submission to FEP-3264 Federated Work Coordination, which supports much more complex project or production planning. Both planning submissions continue the idea of Valueflows extensions to ActivityPub/ActivityStreams based on use case, started by silverpill with FEP-0837 Federated Marketplace.
"},{"location":"fep/c5a1/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/c5a1/#creating-a-to-do","title":"Creating a to-do","text":"A to-do is implemented as a Valueflows Commitment.
Consuming implementations which don't have planning features MAY display plan-related objects similarly to Note objects.
In all cases, the objects can also be discussed using Note objects and replyTo.
Valueflows defines a commitment is defined as A planned economic flow that has been promised by an agent to another agent.
id (REQUIRED): the commitment's unique global identifier.type (REQUIRED): the type of the object MUST be Commitment.attributedTo (REQUIRED): the actor who published the commitment.published (RECOMMENDED): the date and time at which the commitment was published.resourceConformsTo (OPTIONAL): the skill or type of work involved. Could be any URI.effortQuantity (OPTIONAL): the estimated or expected amount and unit of the work. This is an object with two properties:hasUnit (REQUIRED): name of the unit, according to Ontology of units of Measure classification.hasNumericalValue (REQUIRED): amount of the resource.provider (RECOMMENDED): the actor who commits to providing the resource, including doing the work. If not included, it is assumed to be the to actor.receiver (RECOMMENDED): the actor who will be receiving the resource. If not included, it is assumed to be the attributedTo actor.content (RECOMMENDED): the description of what is to be done. The type of content SHOULD be text/html.to (REQUIRED): the audience of the commitment.Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Commitment\": \"vf:Commitment\",\n \"receiver\": \"vf:receiver\",\n \"provider\": \"vf:provider\",\n \"resourceConformsTo\": \"vf:resourceConformsTo\",\n \"effortQuantity\": \"vf:effortQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\"\n }\n ],\n \"type\": \"Create\",\n \"id\": \"https://project.example/activities/ad2f7ee1-6567-413e-a10b-72650cbdc788/create\",\n \"actor\": \"https://project.example/actors/alice\",\n \"object\": {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/todos/ddde9d6f-6f3b-4770-a966-4dkjh8w32e59\",\n \"attributedTo\": \"https://project.example/actors/alice\",\n \"content\": \"Please proofread the document at https://project.example/docs/45, and let me know what you think could be improved.\",\n \"published\": \"2024-05-18T19:22:03.918737Z\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q834191\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"2\"\n },\n \"provider\": \"https://project.example/actors/bob\",\n \"receiver\": \"https://project.example/actors/alice\"\n },\n \"to\": \"https://project.example/actors/bob\"\n}\n"},{"location":"fep/c5a1/#accepting-a-to-do","title":"Accepting a To-do","text":"Accepting or rejecting a to-do is OPTIONAL for this FEP, although it may be required based on the understanding between the actors.
The object of Accept activity MUST be the id of the Commitment object previously sent to the actor.
Activity MAY contain content property for further coordination.
Example:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Accept\",\n \"id\": \"https://project.example/todos/ddde9d6f-6f3b-4770-a966-9kh93jcmljei\",\n \"actor\": \"https://project.example/actors/bob\",\n \"object\": \"https://project.example/todos/ddde9d6f-6f3b-4770-a966-4dkjh8w32e59\",\n \"content\": \"Should be done within a couple days.\",\n \"to\": \"https://project.example/actors/alice\"\n}\n"},{"location":"fep/c5a1/#rejecting-a-to-do","title":"Rejecting a To-do","text":"Accepting or rejecting a to-do is OPTIONAL for this FEP, although it may be required based on the understanding between the actors.
The object of Reject activity MUST be the id of the Commitment object previously sent to the actor.
Activity MAY contain content property indicating the reason for rejection.
Example:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Reject\",\n \"id\": \"https://project.example/todos/8c05f97f-1531-4b70-9ca8-4ee4a09f36a3\",\n \"actor\": \"https://project.example/actors/bob\",\n \"object\": \"https://project.example/todos/ddde9d6f-6f3b-4770-a966-4dkjh8w32e59\",\n \"content\": \"Sorry, my schedule is full for a month.\",\n \"to\": \"https://project.example/actors/alice\"\n}\n"},{"location":"fep/c5a1/#recording-what-actually-was-done","title":"Recording what actually was done","text":"Recording what was done is OPTIONAL for the FEP, although it may be required by the actors involved.
Actual economic activity is represented with an EconomicEvent in Valueflows. In the case of to-do's, it would only occur in response to an earlier to-do commitment. More than one EconomicEvent can be recorded for one Commitment to-do.
The representation of an economic event is a JSON document with the following properties:
id (REQUIRED): the economic event's unique global identifier.type (REQUIRED): the type of the object SHOULD be EconomicEvent. If interoperability with other ActivityPub services is desirable, implementers MAY also use object types from Activity Vocabulary, such as Note.attributedTo (REQUIRED): the actor who published the economic event.content (OPTIONAL): the description of the economic event or communication about the economic event. The type of content SHOULD be text/html.published (RECOMMENDED): the date and time at which the economic event was published.to (REQUIRED): the audience of the economic event.fulfills (REQUIRED): the commitment the economic event is completely or partially fulfilling.resourceConformsTo (OPTIONAL): the type of an economic resource (for to-do's, can be a skill or type of work). Could be any URI. It does not have to match the commitment, but if not included, can be assumed it is the same.effortQuantity (OPTIONAL): the amount and unit of the work done (for to-do's, usually a time quantity). This is an object with two properties:hasUnit (REQUIRED): name of the unit, according to Ontology of units of Measure classification.hasNumericalValue (REQUIRED): amount of the resource.provider (RECOMMENDED): the actor who commits to doing the work. If not included, it is assumed to be the attributedTo actor.receiver (RECOMMENDED): the actor who will be receiving the benefit. If not included, it is assumed to be the to actor.finished (OPTIONAL): set to true if this economic event completes the commitment, or the commitment is no longer open for some reason.{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"EconomicEvent\": \"vf:EconomicEvent\",\n \"fulfills\": \"vf:fulfills\",\n \"receiver\": \"vf:receiver\",\n \"provider\": \"vf:provider\",\n \"resourceConformsTo\": \"vf:resourceConformsTo\",\n \"effortQuantity\": \"vf:effortQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\",\n \"finished\": \"vf:finished\"\n }\n ],\n \"type\": \"Create\",\n \"id\": \"https://project.example/activities/ad2f7ee1-6567-413e-a10b-72650cbdc932/create\",\n \"actor\": \"https://project.example/actors/bob\",\n \"object\": {\n \"type\": \"EconomicEvent\",\n \"id\": \"https://project.example/inputs/ad2f7ee1-6567-413e-a10b-72650cbdc932\",\n \"attributedTo\": \"https://project.example/actors/bob\",\n \"published\": \"2024-10-21T14:16:41.843794Z\",\n \"fulfills\": \"https://project.example/todos/ddde9d6f-6f3b-4770-a966-4dkjh8w32e59\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q3485549\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"1.5\"\n },\n \"provider\": \"https://project.example/actors/bob\",\n \"receiver\": \"https://project.example/actors/alice\",\n \"finished\": true\n },\n \"to\": \"https://project.example/actors/alice\"\n}\n The Commitment can alternatively be marked as finished, without recording an EconomicEvent. This could occur in cases where no work will be done, or work was done but will not be recorded, or some work was recorded earlier without marking the to-do finished. So, finished does not imply that the to-do was done, only that it is not waiting to be done any more.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Commitment\": \"vf:Commitment\",\n \"finished\": \"vf:finished\"\n }\n ],\n \"type\": \"Update\",\n \"id\": \"https://project.example/activities/ad2f7ee1-6567-413e-a10b-72650cbdc932/update\",\n \"actor\": \"https://project.example/actors/alice\",\n \"object\": {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/todos/ddde9d6f-6f3b-4770-a966-4dkjh8w32e59\",\n \"attributedTo\": \"https://project.example/actors/alice\",\n \"published\": \"2024-10-24T16:16:41.843794Z\",\n \"finished\": true\n },\n \"to\": \"https://project.example/actors/alice\"\n}\n"},{"location":"fep/c5a1/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c648/","title":"FEP-c648: Blocked Collection","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@prodromou.nameDRAFT 2023-06-14 #123 - codeberg"},{"location":"fep/c648/#summary","title":"Summary","text":"Users need to review and revise the list of actors they have blocked. This FEP defines a new collection property, the Blocked Collection, which contains the actors that a user has blocked. It also defines a collection of Block activities, which can be used to undo blocks. Finally, it defines inverse properties for both collections, to aid in navigating between the collections and the actors that own them.
The Activity Vocabulary defines a Block activity type. The ActivityPub specification defines how to use a Block activity in the API to block another actor.
However, ActivityPub does not define an efficient way to retrieve the list of actors that a user has blocked. The only way to get this information is to scan the outbox collection for Block activities that were not the object of a later Undo activity. With a large outbox, this can be inefficient and slow.
The followers and following properties of an actor are collections of objects in the actor's social graph. By analogy, the new blocked property defined in this document is a collection of other actors that the actor has blocked.
One use case for the list of blocked actors is to allow the user to review and potentially undo the blocks. However, the ActivityPub specification requires the id of the Block activity to undo the block.
To make it easier to undo blocks, the blocks property of an actor is a collection of Block activities, which include the id of the activity, the object that was blocked. These activities can be used to undo the block.
Of the two properties, blocks is most useful for client applications, since the collection of blocked actors can be derived from the object property of each Block activity. Some implementers, however, may prefer the blocked property for parallelism with the followers and following properties. This specification defines both properties.
The context document for the blocked property is as follows:
{\n \"@context\": {\n \"bl\": \"https://purl.archive.org/socialweb/blocked#\",\n \"blocked\": {\n \"@id\": \"bl:blocked\",\n \"@type\": \"@id\"\n },\n \"blocks\": {\n \"@id\": \"bl:blocks\",\n \"@type\": \"@id\"\n },\n \"blockedOf\": {\n \"@id\": \"https://www.w3.org/ns/activitystreams#blockedOf\",\n \"@type\": \"@id\"\n },\n \"blocksOf\": {\n \"@id\": \"https://www.w3.org/ns/activitystreams#blocksOf\",\n \"@type\": \"@id\"\n },\n }\n}\n The context document is available at the URL https://purl.archive.org/socialweb/blocked.
The main context URL will always have the latest version of the context document for this vocabulary. Additional context URLs are available with version numbers, to allow implementers to define their own level of tolerance to changes in the vocabulary. The version stamps use semantic versioning. The version-stamped context URLs are:
Context URL Purposehttps://purl.archive.org/socialweb/blocked/2.0.0 The exact version of the context used in this document. This alias is useful for implementers that need an immutable document. https://purl.archive.org/socialweb/blocked/2.0 The exact set of terms defined in this document, possibly with bug fixes or documentation changes. This alias is useful to get the latest version of the context document, without introducing any new terms that might conflict with other vocabularies. https://purl.archive.org/socialweb/blocked/2 The set of terms defined in this document, plus possibly new terms, as well as bug fixes and documentation changes. This alias allows implementers to get backwards-compatible changes to the vocabulary, without having to change their code."},{"location":"fep/c648/#properties","title":"Properties","text":""},{"location":"fep/c648/#blocked","title":"blocked","text":"URI https://purl.archive.org/socialweb/blocked#blocked Notes The blocked property of an actor is an ordered collection of ActivityPub actors. The blocked collection SHOULD include all actors blocked by the actor, except for those that have been unblocked by an Undo activity. Each actor in the blocked collection MUST be unique. The blocked collection MUST be sorted in reverse chronological order, with the most recently added actor first. (Users are most likely to want to see who they have blocked recently, so these actors should be ordered first.) As with other ActivityPub properties, the blocked property MAY be referenced in the actor by id or as an embedded node object. Domain Object (an ActivityPub actor) Range OrderedCollection Functional true"},{"location":"fep/c648/#blocks","title":"blocks","text":"URI https://purl.archive.org/socialweb/blocked#blocks Notes The blocks property of an actor is an ordered collection of Block activities. The blocks collection SHOULD include all Block activities by the actor, except for those that have been reverted by an Undo activity. Each activity in the blocks collection MUST be unique. The blocks collection MUST be sorted in reverse chronological order, with the most recent activity first. (Users are most likely to want to see who they have blocked recently, and possibly unblock them, so these activities should be ordered first.) As with other ActivityPub properties, the blocks property MAY be referenced in the actor object by id or as an embedded node object. Domain Object (an ActivityPub actor) Range OrderedCollection Functional true"},{"location":"fep/c648/#blockedof","title":"blockedOf","text":"URI https://purl.archive.org/socialweb/blocked#blockedOf Notes The value of the blockedOf property of a collection is the actor for whom the collection is the value of its blocked property. It is the inverse property of blocked. As with other ActivityPub properties, the blockedOf property MAY be referenced in the actor by id or as an embedded node object. Domain OrderedCollection Range Object (an ActivityPub actor) Functional true"},{"location":"fep/c648/#blocksof","title":"blocksOf","text":"URI https://purl.archive.org/socialweb/blocked#blocksOf Notes The value of the blocksOf property of a collection is the actor for whom the collection is the value of its blocks property. It is the inverse property of blocks. As with other ActivityPub properties, the blocksOf property MAY be referenced in the actor by id or as an embedded node object. Domain OrderedCollection Range Object (an ActivityPub actor) Functional true"},{"location":"fep/c648/#examples","title":"Examples","text":""},{"location":"fep/c648/#blocked-property","title":"blocked property","text":"A publisher can include the blocked collection in the properties of an actor.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/blocked\"\n ],\n \"id\": \"https://example.com/evanp\",\n \"type\": \"Person\",\n \"name\": \"Evan Prodromou\",\n \"inbox\": \"https://example.com/evanp/inbox\",\n \"outbox\": \"https://example.com/evanp/outbox\",\n \"following\": \"https://example.com/evanp/following\",\n \"followers\": \"https://example.com/evanp/followers\",\n \"liked\": \"https://example.com/evanp/liked\",\n \"blocked\": \"https://example.com/evanp/blocked\",\n \"to\": \"as:Public\"\n}\n"},{"location":"fep/c648/#a-blocked-collection","title":"A blocked collection","text":"Retrieving the blocked collection would provide an ordered collection of ActivityPub actors that have been blocked.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/blocked\",\n {\"custom\": \"https://example.com/ns/custom\"}\n ],\n \"id\": \"https://example.com/evanp/blocked\",\n \"type\": \"OrderedCollection\",\n \"attributedTo\": \"https://example.com/evanp\",\n \"blockedOf\": \"https://example.com/evanp\",\n \"name\": \"Evan Prodromou's Blocked Collection\",\n \"orderedItems\": [\n {\n \"type\": \"Person\",\n \"id\": \"https://spam.example/spammer\",\n \"name\": \"Irritating Spammer\"\n },\n {\n \"type\": \"Application\",\n \"id\": \"https://alarmclock.example/alarmclock\",\n \"name\": \"Badly-Behaved Alarm Clock App\"\n }\n ]\n}\n"},{"location":"fep/c648/#blocks-property","title":"blocks property","text":"Similarly, a publisher can include the blocks collection in the properties of an actor. Note that here, the blocks collection is referenced as an embedded node object, rather than a URL. It includes useful metadata about the collection.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/blocked\"\n ],\n \"id\": \"https://example.com/evanp\",\n \"type\": \"Person\",\n \"name\": \"Evan Prodromou\",\n \"inbox\": \"https://example.com/evanp/inbox\",\n \"outbox\": \"https://example.com/evanp/outbox\",\n \"following\": \"https://example.com/evanp/following\",\n \"followers\": \"https://example.com/evanp/followers\",\n \"liked\": \"https://example.com/evanp/liked\",\n \"blocks\": {\n \"id\": \"https://example.com/evanp/blocks\",\n \"type\": \"OrderedCollection\",\n \"totalItems\": 2,\n },\n \"to\": \"as:Public\"\n}\n"},{"location":"fep/c648/#a-blocks-collection","title":"A blocks collection","text":"Retrieving the blocks property value would provide an ordered collection of Block activities that the user has performed and not undone.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/blocked\",\n {\"custom\": \"https://example.com/ns/custom\"}\n ],\n \"id\": \"https://example.com/evanp/blocks\",\n \"type\": \"OrderedCollection\",\n \"attributedTo\": \"https://example.com/evanp\",\n \"blocksOf\": \"https://example.com/evanp\",\n \"name\": \"Evan Prodromou's Blocks Collection\",\n \"orderedItems\": [\n {\n \"type\": \"Block\",\n \"id\": \"https://example.com/evanp/block/2\",\n \"object\": {\n \"type\": \"Person\",\n \"id\": \"https://spam.example/spammer\",\n \"name\": \"Irritating Spammer\"\n },\n \"published\": \"2023-04-15T00:00:00Z\"\n },\n {\n \"type\": [\"custom:Disallow\", \"Block\"],\n \"id\": \"https://example.com/evanp/block/2\",\n \"object\": {\n \"type\": \"Application\",\n \"id\": \"https://alarmclock.example/alarmclock\",\n \"name\": \"Badly-Behaved Alarm Clock App\"\n },\n \"published\": \"2022-12-25T00:00:00Z\"\n }\n ]\n}\n"},{"location":"fep/c648/#processing-requirements","title":"Processing requirements","text":"When a server receives a Block activity from a client through the ActivityPub API, it SHOULD add the activity to the blocks collection of the actor that performed the block. The server MAY also add the blocked actor to the blocked collection of the blocking actor.
When a server receives an Undo activity for a Block activity through the ActivityPub API, it SHOULD remove the Block activity from the blocks collection of the actor that undid the block. The server MAY also remove the blocked actor from the blocked collection of the blocking actor.
The blocked and blocks collections are very sensitive. Actors on the blocked list may be harassing or abusive. If they find themselves on a user's blocklist, they may retaliate against the user. Consequently, the blocked and blocks collections SHOULD NOT be publicly readable.
By default, implementations SHOULD NOT allow read access to the blocked or blocks collections to any actor other than the user that owns the collection.
Some users may want to share their blocklist with other actors. Shared blocklists are an important tool for user safety on monolithic social networks and on the social web. Implementations MAY allow a user to share their blocked collection with other actors. Implementations SHOULD inform the user of the risks of sharing their blocklist with the wrong actors.
Sharing the blocks collection is less useful, since Block activities are usually not readable by anyone but the blocking actor.
The onepage.pub server implements the blocked collection.
activitypub-express implements a blocked property in the streams collection of an actor, including the blocked objects only. The developers' experience was that storing objects only made it hard to Undo a block, since the full Activity object's id is needed. Metadata about the block activity, such as the date, is also lost.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c7d3/","title":"FEP-c7d3: Ownership","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository silverpill @silverpill@mitra.socialWITHDRAWN 2024-06-04 2025-02-21 #323 Discussions codeberg"},{"location":"fep/c7d3/#summary","title":"Summary","text":"In this document we discuss the concept of ownership, as applied to ActivityPub objects.
Warning
This FEP has been superseded by FEP-fe34.
"},{"location":"fep/c7d3/#rationale","title":"Rationale","text":"ActivityPub standard does not specify authentication and authorization mechanisms.
Many implementations use actor and attributedTo properties (defined in Activity Vocabulary) to determine the validity of activities and objects. This proposal attempts to formalize the current practices and provide guidance for implementers.
The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/c7d3/#ownership","title":"Ownership","text":"Ownership is indicated by a property of an ActivityPub object. The name of this property differs depending on the object type:
id property.actor property, which describes the actor that performed the activity. This actor is considered to be the owner of the activity.attributedTo property, which describes the actor to which the object is attributed. This actor is considered to be the owner of the object.owner and controller properties.The owner of an object MUST be an actor.
Warning
According to Activity Vocabulary, actor and attributedTo properties can contain references to multiple actors. These scenarios are not covered by this document and implementers are expected to determine the appropriate authentication and authorization procedures on a case-by-case basis.
Note
In subsequent sections, \"objects\" and \"activities\" will be referred to as simply \"objects\".
"},{"location":"fep/c7d3/#origin","title":"Origin","text":"Object identifiers are grouped together into protection domains called \"origins\". This concept is similar to the \"web origin\" concept described in RFC-6454, and origins of object IDs are computed by the same algorithm.
The same-origin policy determines when a relationship between objects can be trusted.
Note
There might be other ways to establish trust, but they are not covered by this document.
"},{"location":"fep/c7d3/#identifiers-and-ownership","title":"Identifiers and ownership","text":"Identifier of an object and identifier of its owner MUST have the same origin.
"},{"location":"fep/c7d3/#authentication","title":"Authentication","text":"The object is considered authentic if any of the following conditions are met:
POST request contained a valid HTTP signature created using a key whose owner has the same origin as the object owner.If none of these conditions are met, the object MUST be discarded.
If signature verification is performed, the key owner SHOULD match the object owner.
Note
In some cases, consumers can process unauthenticated objects if the risk is deemed acceptable.
"},{"location":"fep/c7d3/#delivered-to-inbox","title":"Delivered to inbox","text":"If the object was delivered to inbox and its authentication fails, the recipient SHOULD fetch it and repeat the authentication procedure.
"},{"location":"fep/c7d3/#emdedded-objects","title":"Emdedded objects","text":"If the embedded and the containing objects have owners with different origins, the authenticity of the embedded object MUST be verified independently either by fetching it from the server of origin, or by verifying its FEP-8b32 integrity proof.
"},{"location":"fep/c7d3/#anonymous-objects","title":"Anonymous objects","text":"An object without an ID can only exist when embedded within another object. It has the same owner as the parent object and it is considered authentic when the parent object is authentic.
"},{"location":"fep/c7d3/#unattributed-objects","title":"Unattributed objects","text":"An object without an explicit owner is owned by the server. Such object MUST be considered authentic only if fetched from the location that has the same origin as its ID.
"},{"location":"fep/c7d3/#authorization","title":"Authorization","text":"The actor that creates the object MUST be its owner.
If activity modifies or deletes an object, its owner SHOULD match the object's owner. If owners are different, their IDs MUST have the same origin.
Examples:
Create, Update and Delete activities, and objects indicated by their object property SHOULD have the same owner.Undo activity and object indicated by its object property SHOULD have the same owner.Add and Remove activities, and objects indicated by their target property SHOULD have the same owner.Announce and Like activities don't modify objects indicated by their object property, therefore their owners can be different.When ownership changes, the new owner ID MUST have the same origin as the old owner ID.
"},{"location":"fep/c7d3/#access-control","title":"Access control","text":"When a protected object is fetched, the GET request MUST contain a HTTP signature created using a key whose owner SHOULD belong to object's intended audience. If key owner doesn't belong to intended audience, its ID MUST have the same origin as one of the actors in object's intended audience.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c893/","title":"FEP-c893: DOAP","text":"Authors Status Date received Tracking issue Discussions Repository AvidSeeker avidseeker7@protonmail.comDRAFT 2024-07-15 #372 Discussions codeberg"},{"location":"fep/c893/#summary","title":"Summary","text":"This proposal introduces a standardized method for describing Fediverse projects using the Description of a Project (DOAP) format. The proposal outlines the creation of doap.jsonld file that includes details about implemented federation protocols and supported Fediverse Enhancement Proposals (FEPs). This makes it easier for developers and users to understand the capabilities and compatibility of various Fediverse projects.
doap.jsonld File","text":"Here is an example structure for the doap.jsonld file:
{\n \"@context\": {\n \"doap\": \"http://usefulinc.com/ns/doap#\",\n \"foaf\": \"http://xmlns.com/foaf/0.1/\",\n \"rdfs\": \"http://www.w3.org/2000/01/rdf-schema#\",\n \"doap:description\": {\n \"@id\": \"doap:description\",\n \"@container\": \"@language\"\n },\n \"doap:shortdesc\": {\n \"@id\": \"doap:shortdesc\",\n \"@container\": \"@language\"\n }\n },\n \"@type\": \"doap:Project\",\n \"doap:name\": \"ExampleProject\",\n \"doap:homepage\": \"https://example.org\",\n \"doap:description\": {\n \"en\": \"ExampleProject is a Fediverse client/server that supports multiple protocols and enhancements.\",\n \"es\": \"ExampleProject es un cliente/servidor de Fediverse que admite m\u00faltiples protocolos y mejoras.\"\n },\n \"doap:shortdesc\": {\n \"en\": \"Tools and vocabulary for describing community-based software projects.\",\n \"es\": \"Vocabulario y herramientas para describir proyectos de software comunitarios.\"\n },\n \"doap:created\": \"2022-01-01\",\n \"doap:logo\": \"https://example.org/logo.png\",\n \"doap:screenshots\": [\n \"https://example.org/screenshot1.png\",\n \"https://example.org/screenshot2.png\"\n ],\n \"doap:category\": [\n \"http://software.freshmeat.net/browse/1020/\",\n \"http://osdir.com/Downloads+index-req-viewsdownload-sid-201.phtml\"\n ],\n \"doap:repository\": {\n \"@type\": \"doap:GitRepository\",\n \"doap:browse\": \"https://github.com/example/exampleproject/\",\n \"doap:location\": \"https://github.com/example/exampleproject.git\"\n },\n \"doap:release\": {\n \"@type\": \"doap:Version\",\n \"doap:created\": \"2024-07-15\",\n \"doap:name\": \"v1.0.0\",\n \"doap:revision\": \"1.0.0\"\n },\n \"doap:maintainer\": [\n {\n \"@type\": \"foaf:Person\",\n \"foaf:name\": \"John Doe\",\n \"foaf:homepage\": \"https://github.com/johndoe\"\n },\n {\n \"@type\": \"foaf:Person\",\n \"foaf:name\": \"Jane Smith\",\n \"foaf:homepage\": \"https://github.com/janesmith\"\n }\n ],\n \"doap:implements\": [\n \"https://activitypub.rocks/specification\",\n \"https://diasporafoundation.org\"\n ],\n \"doap:supportedFEPs\": [\n \"https://fediverse.org/fep-0001\",\n \"https://fediverse.org/fep-0002\"\n ]\n}\n"},{"location":"fep/c893/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/cb76/","title":"FEP-cb76: Content Addressed Vocabulary","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository a a@trwnh.comWITHDRAWN 2022-11-29 2025-04-07 #41 Discussions codeberg"},{"location":"fep/cb76/#summary","title":"Summary","text":"JSON-LD context definitions typically live at some URI which gets used as a namespace. It is generally expected that the URI is long-lived, and often the context document is retrievable from that URI, but sometimes these links break due to technical errors, expired domains, and other such issues. This FEP proposes adopting a solution proposed by [CAV] for any extension terms defined within other FEPs, as well as optionally for standard vocabulary.
"},{"location":"fep/cb76/#1-defining-fep-extension-terms","title":"1. Defining FEP extension terms","text":"Each extension term MUST have a specification text.
This specification is then used to calculate a SHA256 hash, which can be used as a URN within @context in lieu of a namespaced property.
Say we want to define a new ActivityStreams Vocabulary [AS2V] term, which will be a sub-type of Activity called Message, with the following specification text:
Indicates that the actor is sending a direct message.\n We obtain the SHA256 hash of this specification text:
$ echo -n \"Indicates that the actor is sending a direct message.\" | sha256sum\n\nbab53e61faa0ddecce6991df4c26259b6c2e1b880cef12225033590fcaad1aaa -\n We can now use this hash to extend ActivityPub [AP] with a Message activity:
{\n\"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"Message\": \"urn:sha256:bab53e61faa0ddecce6991df4c26259b6c2e1b880cef12225033590fcaad1aaa\"\n }\n],\n\"actor\": \"https://social.example/~alice\",\n\"type\": \"Message\",\n\"to\": \"https://bob.example.com\",\n\"content\": \"hi friend\"\n}\n"},{"location":"fep/cb76/#2-storing-fep-extension-terms-within-the-fep-git-repository","title":"2. Storing FEP extension terms within the FEP Git repository","text":"(This section is non-normative.)
A directory named context/ should be created to store content addressed vocabulary extensions associated with FEPs. FEPs that propose vocabulary extensions MUST create a text file called fep-abcd_property.txt within this directory, where abcd is the identifier of the associated FEP, property is the recommended shorthand name of the property, and the contents of the text file are the specification text. FEP authors SHOULD try to use unique property names within the existing FEP vocabulary, unless an FEP is intended to supersede or replace an older FEP.
A context.jsonld file SHOULD be generated any time a new vocabulary term is finalized, with its contents being a definition of every finalized vocabulary term.
In Example 1, we defined a Message activity type to be used for ActivityPub [AP] direct messaging. Say we now want to draft an FEP for this definition.
We follow the FEP process as defined in FEP-a4ed to title and identify our FEP proposal. Applying this process to our proposal of A Message activity for direct messaging yields the identifier FEP-0ac6. Combined with our specification text of Indicates that the actor is sending a direct message, this yields the following text file fep-0ac6_Message.txt:
Indicates that the actor is sending a direct message\n The text file is then stored at context/fep-0ac6_Message.txt, and when it is finalized, its SHA256 hash is added to context/context.jsonld:
{\n \"@context\": {\n \"Message\": \"urn:sha256:bab53e61faa0ddecce6991df4c26259b6c2e1b880cef12225033590fcaad1aaa\"\n }\n}\n"},{"location":"fep/cb76/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/cd47/","title":"FEP-cd47: Federation-friendly Addressing and Deduplication Use-Cases","text":"Authors Status Date received Tracking issue Discussions Repository Bumblefudge bumblefudge@learningproof.xyzDRAFT 2024-05-31 #319 Discussions codeberg"},{"location":"fep/cd47/#summary","title":"Summary","text":"A proposed taxonomy of ways to make various kinds of ActivityPub data identifiable across locations to simplify higher-order functions like moderation receipts, tracking for trust and safety purposes, data migration, compliance, etc. This is intended as a light-weight and informational/meta-technical design document, not a specification or an extension.
"},{"location":"fep/cd47/#rationale","title":"Rationale","text":"While Camille Fran\u00e7oise's originial \"ABCs\" paper was focused on disinformation specifically, it lays out the basic taxonomy I will be using here between Actors, Behavior, and Content, as three different vectors for disinformation, but also for abuse, trust and safety issues, inauthentic activity, copyright compliance, hate speech compliance, etc. The goal of this document is to collect the addressable and deduplication requirements for all of these related \"content-specific\" liabilities and use-cases, and inform the design of addressing and deduplication primitives in future FEPs.
"},{"location":"fep/cd47/#terminology","title":"Terminology","text":"If we scope this exercise to data conformant to the ActivityStreams data model as extended and federated by ActivityPub, we could scope these categories as:
Actors: fediverse \"accounts\", bots, etc.Note object, for example), media \"attachments\" (which we could further constrain to defined media-types in the HTML sense, for simplicity), and links out to other data (URLs, but also other URIs TBD?)What we mean by \"addressing\" given the scope defined above is tricky, because there is a URI-based system of ids inherent to the JSON-LD data model that the AS and AP specifications build on, and most references to addressing or identification in the core specifications refer only to this graph-friendly but static scheme of resources and links. In practice to date, this has been coextensive with HTTPS URLs and DNS-resolveable domains. Whether non-HTTPS URLs could or should be used as ids or @ids is out-of-scope of this use-case document, and should be considered a design/implementation decision treated elsewhere. Regardless, today's Fediverse data is overwhelmingly (perhaps exclusively?) addressed by domain-based (and domain-dependent) ids that any new system should be able to handle and deduplicate in its current form, at least as \"legacy\" data if any breaking changes were proposed to these practices or to the core specifications.
Today, most actors and behaviors are addressed by their ids that are also their current HTTPS \"location\"; content is similarly location-addressed most of the time, except insofar as some attachments are sometimes renamed in bucket storage configurations. All of these addresses are brittle vis-a-vis duplication, migration, and \"server death\"-- moving them to a new location creates a new address for identical content/referent, often without a \"forwarding\" redirection (HTTP-code-based or otherwise), or a backlink to its previous address, while a server going down suddenly just breaks all those links with no verifiable remedy.
One form of location-independent addressing is \"content-addressing\", the most common form of which is addressing canonicalizable contents by hashing them in canonicalized form, often used in key/value stores, package managers, etc. It can sometimes be trickier than expected, however, to excise the location (or all properties that break if location changes) from the canonical form hashed to identify a piece of content, or an actor or a specific behavior.
"},{"location":"fep/cd47/#high-level-use-cases","title":"High-level Use-Cases","text":"id patterns, annotations, bucket-storage URL schemes, etc.id!) are perhaps the simplest to content-address. These do not need to be addressed by unknown, external, or future parties, but MAY benefit from addressing the same way other content is addressed, e.g. when string-comparing uploads or pre-published content to a content-addressed blocklist before publishing.@Context files or javascript files, Emoji sets, or other shared resources that might mutate at a static address be brought into scope? Is it redundant or dangerous to have similar-but-different content addressing mechanisms for those kinds of resource verifiability running parallel to Actor, Behavior, and Content verifiability?ids? How to deduplicate across migrations that change id property?Normative
Informational
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/d36d/","title":"Index","text":"hide: - navigation description: \"New instances on the threadiverse (servers that implement ActivityPub with FEP-1b12) are often seeded with forums for common interests, leading to multiple servers having similar forums. Users may dislike having to follow what they perceive to be \"duplicate\" forums or keep up with multiple discussions on the same topic across multiple servers. This document describes a method for allowing Group actors to share content to reduce posting of a single link multiple times, which reduces what users see as \"duplicate\" posts and fragmented conversations across multiple forums.\"
"},{"location":"fep/d36d/#fep-d36d-sharing-content-across-federated-forums","title":"FEP-d36d: Sharing Content Across Federated Forums","text":"Authors Status Date received Tracking issue Discussions Repository Zack Dunn zack@tilde.teamDRAFT 2023-07-01 #125 - codeberg"},{"location":"fep/d36d/#summary","title":"Summary","text":"New instances on the threadiverse (servers that implement ActivityPub with FEP-1b12) are often seeded with forums for common interests, leading to multiple servers having similar forums. Users may dislike having to follow what they perceive to be \"duplicate\" forums or keep up with multiple discussions on the same topic across multiple servers. This document describes a method for allowing Group actors to share content to reduce posting of a single link multiple times, which reduces what users see as \"duplicate\" posts and fragmented conversations across multiple forums.
FEP-1b12 introduces federated forums and is implemented by Lemmy, /kbin, and Friendica.
The site reddit has a feature for grouping its forums, called subreddits, into a new forum, called a multireddit. A multireddit is a feed made up of the combination of each of its constituent subreddits and allows a user an easy way to keep up with multiple related forums. Because subreddits can be in multiple multireddits, multireddits don't affect moderation of links posted to individual subreddits and a link can be posted to more than one constituent subreddit within a multireddit.
"},{"location":"fep/d36d/#group-to-group-follows","title":"Group to Group Follows","text":"When a moderator of a federated forum determines that their forum overlaps in topic with another forum, they can direct the Group actor to send a Follow activity to the other forum's Group actor. FEP-1b12 specifices that a group should automatically respond with an Accept/Follow, but this document overrides that for Follow activities with an actor of type Group. After receiving a Follow activity from another Group, the group MAY automatically respond with an Accept/Follow or a moderator may instruct the group to reply with a Reject/Follow. After replying with a Accept/Follow activity, the group that received the Follow activity MAY automatically add the first group to its following collection, creating a symmetric relationship.
This document makes no change to the handling of an Undo/Follow activity. If a group receives an Undo/Follow from a Group actor, it MAY automatically remove the other group from its following collection.
When a group receives an activity in its inbox, it SHOULD perform automatic validation as described in FEP-1b12. If that validation includes deduplication (via the url property of the activity's object, the url of any attachements, or any other method), that deduplication validation MUST include objects received from followed groups. If an activity fails this deduplication validation, the group MUST respond with a Reject activity where the object property is the object from the inbox activity and the target object is the object that the new object duplicates. This ensures that content is posted only once across related forums and a forum can provide navigation to an original post when a user tries to post a duplicate.
If the incoming activity is deemed valid, the group MUST handle it according to FEP-1b12 handling of valid activities.
"},{"location":"fep/d36d/#references","title":"References","text":"[FEP-1b12] Felix Ableitner, FEP-1b12: Group federation
"},{"location":"fep/d36d/#copyright","title":"Copyright","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/d556/","title":"FEP-d556: Server-Level Actor Discovery Using WebFinger","text":"Authors Status Type Date received Date final Tracking issue Discussions Repository Steve Bate svc-fep@stevebate.netFINAL implementation 2024-01-20 2025-03-15 #243 - codeberg"},{"location":"fep/d556/#summary","title":"Summary","text":"Server-level ActivityPub actors support server-wide functionality rather than representing a user or the software equivalent (sometimes called a bot). This proposal describes how to discover a server-level actor's URI using WebFinger.
"},{"location":"fep/d556/#terminology","title":"Terminology","text":"The term server is not well-defined. For the purposes of this document, an server is an origin SameOriginPolicy having the same URL prefix (scheme, host, port). The term does not imply anything about network or software architecture. An server could consist of many server processes behind a load-balancing reverse proxy. Or, inversely, a single server process could host many servers (multi-tenant architecture).
Some implementations could have multiple actors to support different server-level roles (moderation, administration, etc.). In this document, the term server-level actor will be used to describe these kind of actors. The term Server Actor or Application Actor is a special, but common, case where there is a single server-level actor.
The term Server is used extensively in the ActivityPub Recommendation, although it is mostly undefined beyond which activities a server may process. The term is closely related to Mastodon's use of the word instance, although this is not the only way the word is used in online discussions.
NOTE: The standard role and responsibilities of server-level actors are not defined here (or elsewhere, at the time of this submission). Several implementations have something they call an Instance Actor or Application Actor, but they may or may not be interoperable since no standard behaviors have been defined at this time.
"},{"location":"fep/d556/#use-cases","title":"Use Cases","text":"Although this FEP does not define specific uses of server-level actors, it's useful to know how they are, or could be, used in practice. The following are a some potential use cases:
Signing Fetch Requests: This appears to be the most common use case. The requirement for this is a combination of: limiting access to actor profiles by requiring HTTP Signatures (i.e., \"authorized fetch\") and tightly-coupling actor profiles with their public keys. This results in profile/key fetch loops. (InstanceActor). To mitigate this undesirable behavior, one technique is to have a third-party actor (often called an \"instance actor\") sign every fetch request. Actor discovery is not required for this use case, but it's mentioned here because it appears to be the motiving use case for FEP-2677, which has some similarities to this one.
Relay Support: A server-level actor can be used for subscribing to a relay (often using an ActivityPub Follow request) and receiving inbox messages.
Server-level Subscriptions: Some implementations, like Pleroma, provide an actor that can be followed to receive all messages from an \"instance\".
Moderation: A server-level actor may be used to federate moderation-related content (actor or domain blocks, post flags, etc.) or provide a publication proxy to shield the identity of moderators performing the actions.
Announcements: An server-level actor could be used to public server news. For example, it could publish content including announcements about new features, maintenance schedules, or updates.
Object Attribution: Some server implementations allow some objects to be attributed to the server rather than an individual user or account.
Administration: A server-level actor could be used to share information about software issue (including reports from users), available updates, and security vulnerabilities and mitigations.
To discover an server-level actor's URI, query WebFinger with the server prefix as the resource query parameter.
Example Request:
GET /.well-known/webfinger?resource=https://server.example/\n Response: {\n \"subject\": \"https://server.example/\",\n \"links\": [\n {\n \"rel\": \"https://www.w3.org/ns/activitystreams#Service\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://server.example/actor\"\n }\n ]\n}\n The subject would typically be the resource URI. This proposal does not depend on any specific URI for subject, although the ActivityPub actor URI is recommended. The Server-level Actor's URI will be the href property of a link with a rel (relation type) property of https://www.w3.org/ns/activitystreams#Service (W3C AS2 Service Primer). The type of the Server-level Actor itself is not required to be the same as the relation type.
The https://www.w3.org/ns/activitystreams#Service rel value may be replaced with self if there is no ambiguity between the server-level actor and user's actor in a single actor server (see discussion of single-actor servers).
A http://webfinger.net/rel/profile-page rel (WebFinger Relations) can be used to link to server metadata (possibly with multiple content types). However, the structure of the target metadata has not been defined at this time. For example, the following links refer to profile data in HTML and JSON-LD formats.
{\n \"subject\": \"https://server.example/\",\n \"links\": [\n {\n \"rel\": \"https://www.w3.org/ns/activitystreams#Service\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://server.example/actor\"\n },\n {\n \"rel\": \"http://webfinger.net/rel/profile-page\",\n \"type\": \"text/html\",\n \"href\": \"https://server.example/profile\"\n },\n {\n \"rel\": \"http://webfinger.net/rel/profile-page\",\n \"type\": \"application/ld+json\",\n \"href\": \"https://server.example/profile\"\n }\n ]\n}\n If multiple server-level actor links are returned, the links can be disambiguated by adding metadata to the links using standard WebFinger properties. For example, an implementation could have different server-level actors that serve different purposes.
It's also possible that another FEP will define standard rel URIs for common roles. In that case, those FEP role URIs SHOULD be preferred.
NOTE: The definition of standard server-level actor roles is outside the scope of this FEP.
{\n \"subject\": \"https://server.example/\",\n \"links\": [\n {\n \"rel\": \"https://www.w3.org/ns/activitystreams#Service\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://server.example/actor\",\n \"properties\": {\n \"http://schema.org/roleName\": \"administration\"\n }\n },\n {\n \"rel\": \"https://www.w3.org/ns/activitystreams#Service\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://server.example/actor\",\n \"properties\": {\n \"http://schema.org/roleName\": \"moderation\"\n }\n }\n ]\n}\n In this example, the same actor used used for administration and moderation. However, the example would also be valid if the actors were different. It's possible that for some use cases a role might be further refined. For example, additional properties might specify a geographical region for a role.
"},{"location":"fep/d556/#single-actor-servers","title":"Single Actor Servers","text":"A developer of a single-actor (user actor) server may want that user to have a URI corresponding to the server prefix although it's not intended to be an server-level actor. This scenario, which is not expected to be a common one, can be supported by returning multiple links in the WebFinger response.
{\n \"subject\": \"https://server.example/\",\n \"links\": [\n {\n \"rel\": \"https://www.w3.org/ns/activitystreams#Service\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://server.example/server-actor\"\n },\n {\n \"rel\": \"self\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://server.example/user-actor\"\n }\n ]\n}\n If an application is only interested in a the Server Actor or User Actor specifically, it can use the rel query parameter to filter the links, as described in the WebFinger specification (if supported by the Webfinger service implementation).
For example, to only query the User Actor URI, the query would be:
GET /.well-known/webfinger?resource=https://server.example/&rel=self\n {\n \"subject\": \"https://server.example/\",\n \"links\": [\n {\n \"rel\": \"self\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://server.example/user-actor\"\n }\n ]\n}\n"},{"location":"fep/d556/#implementations","title":"Implementations","text":"Known implementations include:
GET /.well-known/webfinger?resource=https://mastodon.social/\nHost: https://mastodon.social\n or using Mastodon account-based URI: GET /.well-known/webfinger?resource=acct:mastodon.social@mastodon.social\nHost: https://mastodon.social\n {\n \"subject\": \"acct:mastodon.social@mastodon.social\",\n \"aliases\": [\n \"https://mastodon.social/actor\"\n ],\n \"links\": [\n {\n \"rel\": \"http://webfinger.net/rel/profile-page\",\n \"type\": \"text/html\",\n \"href\": \"https://mastodon.social/about/more?instance_actor=true\"\n },\n {\n \"rel\": \"self\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://mastodon.social/actor\"\n },\n {\n \"rel\": \"http://ostatus.org/schema/1.0/subscribe\",\n \"template\": \"https://mastodon.social/authorize_interaction?uri={uri}\"\n }\n ]\n}\n Some differences between the Mastodon implementation and this proposal include:
It does not support standard WebFinger filtering by rel.
The subject is the Mastodon-specific account URI for the server-level actor rather than the recommended ActivityPub actor URI.
Since no user-related actor link is provided for the server resource, the self rel value can be used without ambiguity
FEP-2677 suggests using NodeInfo for a similar purpose. There are several disadvantages of this compared to using WebFinger.
rel relation to the NodeInfo index document. This may have surprising effects on some consuming implementations. This proposal is using WebFinger in standard ways.rel value, a as:Service (Primer) is the type suggested by the W3C ActivityStreams Primers for this kind of resource rather than as:Application (Primer). (Note this is distinct from the type specified in the server-level actor resource that's linked from WebFinger.)as:Application type. This proposal has no constraints on the actor type. The as:Service URI is only used for the link relation type.Although the definition isn't clear, the \"Application Actor\" in FEP-2677 appears to be a proxy for a software \"application\" (not defined, but appears to be a similar concept to \"server\" in this proposal). For example, there's a discussion about attaching application metadata to the actor. In this proposal, there is no server proxy actor (although that's not prohibited). There is a server WebFinger resource with linked server-level service actors, but the server resource is not necessarily an actor itself.
FEP-2c59 discusses how to discover WebFinger resource URIs from an ActivityPub actor resource. This is not related to server-level actor discovery.
FEP-4adb discusses dereferencing identifiers with WebFinger. It's similar to this proposal but not specifically related to discovering server-level actors.
"},{"location":"fep/d556/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/d767/","title":"FEP-d767: Extend ActivityPub with Valueflows","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Lynn Foster lynnfoster@social.coopWITHDRAWN 2023-04-02 2024-02-03 #76 - codeberg"},{"location":"fep/d767/#summary","title":"Summary","text":"A standard method to extend ActivityPub/ActivityStream with Valueflows vocabulary, to enable varied economic networking activity in the fediverse.
"},{"location":"fep/d767/#history","title":"History","text":"Valueflows was started in 2015 by a group of developers who met around the concept of an \"open app ecosystem\". The goal was to define one of the vocabularies needed to enable modular apps and components to communicate in a standard way, using distributed architecture. By extension, the goal was for people and organizations to more easily work together to coordinate the creation, distribution, and exchange of economic resources.
Valueflows got a first version out in 2017, and started a period of learning from implementations.
Valueflows met the fediverse through Mayel de Borniol and Ivan Minutillo, and work was begun in 2017 to integrate Valueflows into the software that became Bonfire.
Valueflows would like to get to a stable v1.0 this year. The core is stable, but there are several features around the edges that are either not yet implemented or not yet tested enough.
"},{"location":"fep/d767/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this specification are to be interpreted as described in [RFC-2119].
Fediverse software that supports economic activity MAY implement the Valueflows extension.
"},{"location":"fep/d767/#vocabulary-integration","title":"Vocabulary Integration","text":"Note this is not at all finalized, will be seeking feedback in SocialHub.
The following general patterns MUST be followed to integrate Valueflows objects into ActivityPub messages.
The general idea is that AP/AS will be used basically for messaging. Valueflows does have some concepts that could be considered activities, but they don't fit nicely into the current list.
Note: The overlap between the Agent portion of the Valueflows model and the AP actors will not be addressed at this time, due to the complexity of mapping to the actor model, as well as the in-process suggestions for AP groups, organizations, communities, etc. Until it is explicitly addressed, when there is overlap, any Person, Group, Organization actor with agency can be used as a Valueflows Agent. In addition, Valueflows will include Agents that are not AP actors, and these can be handled with the standard Create, Update, Delete.
Examples (loosely documented atm, just for discussion):
{\n \"@context\": {\n \"https://www.w3.org/ns/activitystreams\",\n \"vf\": \"https://w3id.org/valueflows/\",\n },\n \"summary\": \"Sally created a project plan.\",\n \"type\": \"Create\",\n \"actor\": {\n \"type\": \"Person\",\n \"name\": \"Sally\"\n },\n \"object\": {\n \"type\": \"vf:Plan\",\n \"name\": \"Define AP-VF Extension\",\n \"content\": \"Collaborate on defining the VF extension to AP/AS. Propose as a FEP (Fediverse Enhancement Proposal).\" # or use skos:note per vf, different meanings?\n }\n}\n\n{\n \"@context\": {\n \"https://www.w3.org/ns/activitystreams\",\n \"vf\": \"https://w3id.org/valueflows/\",\n },\n \"summary\": \"Sally created a process.\",\n \"type\": \"Create\",\n \"actor\": {\n \"type\": \"Person\",\n \"name\": \"Sally\"\n },\n \"object\": {\n \"type\": \"vf:Process\",\n \"vf:name\": \"Draft AP-VF examples\",\n \"content\": \"Collaborate on examples to get discussion going on the VF extension to AP/AS.\",\n \"vf:inScopeOf\": {\n \"type\": \"Organization\",\n \"vf:name\": \"HUMANs\", # could use target for this?\n },\n \"vf:plannedWithin\": {\n \"type\": \"Plan\",\n \"vf:name\": \"Define AP-VF Extension\", # this would be an id?\n },\n \"vf:outputs\": {\n \"object\": {\n \"type\": \"vf:Commitment\",\n \"vf:action\": \"produce\",\n \"vf:conformsTo\": {\n \"type\": \"ResourceSpecification\",\n \"vf:name\": \"Vocabulary Spec\"\n },\n \"vf:due\": \"2023-05-08T10:30:00-5:00\",\n \"vf:provider\": {\n \"type\": \"Organization\", # vf? as?\n \"vf:name\": \"HUMANs\"\n },\n \"vf:receiver\": {\n \"type\": \"Organization\",\n \"vf:name\": \"SocialHub\"\n }\n }\n }\n }\n}\n\n{\n \"@context\": {\n \"https://www.w3.org/ns/activitystreams\",\n \"vf\": \"https://w3id.org/valueflows/\",\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\"\n },\n \"summary\": \"Lynn created a commitment.\",\n \"type\": \"Create\",\n \"actor\": {\n \"type\": \"Person\",\n \"name\": \"Lynn\"\n },\n \"object\": {\n \"type\": \"vf:Commitment\",\n \"vf:inputOf\": {\n \"type\": \"vf:Process\",\n \"vf:name\": \"Draft AP-VF examples\"\n }\n \"vf:action\": \"work\",\n \"vf:conformsTo\": {\n \"type\": \"ResourceSpecification\",\n \"vf:name\": \"Vocabulary Work\"\n },\n \"vf:effortQuantity\": {\n \"type\": \"om:Measure\",\n \"om2:hasNumericalValue\": 10,\n \"om2:hasUnit\": \"hr\"\n }\n \"vf:due\": \"2023-05-01T10:30:00-5:00\",\n \"vf:provider\": {\n \"type\": \"Person\", # vf? as?\n \"vf:name\": \"Lynn\"\n },\n \"vf:receiver\": {\n \"type\": \"Organization\",\n \"vf:name\": \"HUMANs\"\n },\n \"content\": \"First draft of examples, submit for feedback.\" # skos:note?\n }\n}\n"},{"location":"fep/d767/#only-stable-parts-of-the-valueflows-vocabulary-will-be-addressed","title":"Only stable parts of the Valueflows vocabulary will be addressed","text":""},{"location":"fep/d767/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/d8c2/","title":"FEP-d8c2: OAuth 2.0 Profile for the ActivityPub API","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evanp@socialwebfoundation.orgDRAFT 2023-09-17 #165 Discussions codeberg"},{"location":"fep/d8c2/#summary","title":"Summary","text":"This FEP defines a mechanism for using an ActivityPub object ID as the client_id in the OAuth 2.0 authorization code flow.
(An earlier version defined a full profile for using OAuth 2.0 with the ActivityPub API, but this version has been abbreviated to focus only on the client ID mechanism. The title has been retained to accommodate FEP tooling.)
"},{"location":"fep/d8c2/#motivation","title":"Motivation","text":"ActivityPub defines the ActivityPub API, a RESTful HTTP API for stream-oriented social software. This API allows client software to read ActivityPub objects, including actors, collections, activities, and content objects. Client software can also create new Activity objects by posting to an actor's outbox collection (also called \"client-to-server\" or \"c2s\").
The ActivityPub specification does not define an authorization mechanism for the API, although the ActivityPub Primer Authorization and Authentication recommendations include some suggestions. Although there are many ways to implement client authorization for an API, OAuth 2.0 is a popular and well-understood framework.
OAuth 2.0 is broad and encompasses a number of different techniques and use cases. OAuth 2.0 Simplified documents the most common profile of OAuth 2.0: authorization code flow and bearer tokens. Many OAuth 2.0 client libraries implement this profile.
The OAuth 2.0 authorization code flow requires two main endpoints for a client to initiate the flow: an authorization endpoint and a token endpoint. These can be discovered using the endpoints property of the ActivityPub actor or the Authorization Server Metadata endpoint from RFC 8414.
The OAuth 2.0 flow uses a client identifier to show important information about the client software to the user, and to avoid certain classes of spoofing attacks.
A common use case for OAuth 2.0 is an API supplied by a single provider. With a single provider, the client developer can register a client ID out of band using the provider's developer Web site or other tools.
With multiple providers, as with the Fediverse, out-of-band registration becomes untenable. With tens of thousands of known ActivityPub servers on the Internet, client developers cannot manually register client IDs with each provider of the ActivityPub API.
One option is to use Dynamic Client Registration protocol from RFC 7591. This defines a standard HTTP endpoint used for registering an application with an authorization server and receiving a unique client identifier.
Dynamic client registration adds some extra complexity on the client side. In particular, client software has to maintain a record of the correct client ID for each authorization server used.
This profile addresses these issues by using a single, well-defined ActivityPub object to identify and describe the client software.
"},{"location":"fep/d8c2/#client-identifier","title":"Client identifier","text":"ActivityPub provides a rich vocabulary for describing objects in the social space. Each object in the ActivityPub world has a unique https: URI, which must be dereferenceable to a JSON-LD document describing the object.
This allows a distributed description of ActivityPub API clients that doesn't require out-of-band registration.
Objects dereferenced at the id SHOULD be of type Application or Service. They MUST have an id property with the same value as the client_id parameter. They MUST have a redirectURI property with the redirect URI for the client (see Context document below).
Clients SHOULD provide metadata to help users make authorization decisions, including:
nameMap or name: The name of the client software.icon: An Image object with the icon for the client software.summaryMap or summary: A description of the application or service.attributedTo: The name, id, icon and summary properties of the actor responsible for the client software.Support for using ActivityPub object IDs as OAuth 2.0 client IDs can be declared in two ways.
"},{"location":"fep/d8c2/#actor-discovery","title":"Actor discovery","text":"An ActivityPub actor can include the objectIDAsClientID property. If true, client software can use the client ID format in this specification to identify themselves to authorization servers.
An authorization server can declare its support for ActivityPub object IDs as client IDs by adding the activitypub_object_id_as_client_id flag to its Authorization Server Metadata.
The context document for this specification is at https://purl.archive.org/socialweb/oauth/2.0. Its contents are as follows:
{\n \"@context\": {\n \"oauth\": \"https://purl.archive.org/socialweb/oauth#\",\n \"redirectURI\": {\n \"@id\": \"oauth:redirectURI\",\n \"@type\": \"xsd:anyURI\"\n },\n \"objectIDAsClientID\": {\n \"@id\": \"oauth:objectIDAsClientID\",\n \"@type\": \"xsd:boolean\"\n }\n }\n}\n"},{"location":"fep/d8c2/#context-url-aliases","title":"Context URL aliases","text":"Aliases are provided for the context URL to allow change over time with backwards compatibility, using a semantic versioning strategy.
https://purl.archive.org/socialweb/oauth/2.0.0 This URL will be bytewise stable, and can be used for clients that use digital signatures or hashes to validate context URLs.https://purl.archive.org/socialweb/oauth/2.0 The preferred URL. Backwards-compatible changes, such as whitespace and formatting, may be made, but no new terms will be added and none will be removed or modified. Will be kept up to date with the latest 2.0.x version.https://purl.archive.org/socialweb/oauth/2 New terms may be added, but none will be removed or modified. Will be kept up to date with the latest 2.x.x version.https://purl.archive.org/socialweb/oauth The latest version of the context document; backwards-incompatible changes may be applied, such as removing or modifying terms.New versions of the context document will increment the major, minor and patch version as needed.
"},{"location":"fep/d8c2/#properties","title":"Properties","text":""},{"location":"fep/d8c2/#redirecturi","title":"redirectURI","text":"The redirectURI property is an IRI that the client uses to receive the authorization code after the user authorizes the client. The server MUST verify that the redirect_uri parameter in the authorization request matches the redirectURI property of the client object.
This flag has a boolean value, true or false. If true, the authorization server for the actor with this property supports using ActivityPub object IDs as client IDs, as described in this document.
The following actor description declares that the actor's authorization server supports ActivityPub object IDs as OAuth 2.0 client IDs using the objectIDAsClientID flag.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/oauth/2.0\"\n ],\n \"id\": \"https://social.example/user/evan\",\n \"inbox\": \"https://social.example/user/evan/inbox\",\n \"outbox\": \"https://social.example/user/evan/outbox\",\n \"endpoints\": {\n \"oauthAuthorizationEndpoint\": \"https://social.example/authorize\",\n \"oauthTokenEndpoint\": \"https://social.example/token\"\n },\n \"objectIDAsClientID\": true\n}\n"},{"location":"fep/d8c2/#authorization-server-metadata-flag","title":"Authorization Server Metadata flag","text":"An authorization server can declare its support for using ActivityPub object IDs as client IDs with the activitypub_object_id_as_client_id flag.
{\n \"issuer\": \"https://social.example\",\n \"authorization_endpoint\": \"https://social.example/authorize\",\n \"token_endpoint\": \"https://social.example/token\",\n \"registration_endpoint\": \"https://social.example/registration\",\n \"scopes_supported\": [\n \"read\",\n \"write\"\n ],\n \"response_types_supported\": [\n \"code\"\n ],\n \"grant_types_supported\": [\n \"authorization_code\",\n \"refresh_token\"\n ],\n \"code_challenge_methods_supported\": [\n \"S256\"\n ],\n \"token_endpoint_auth_methods_supported\": [\n \"none\"\n ],\n \"activitypub_object_id_as_client_id\": true\n}\n"},{"location":"fep/d8c2/#follower-recommender","title":"Follower recommender","text":"A Web service that wants to use the ActivityPub API would define an ActivityPub object at https://followrec.example/client. This object has a redirectURI property with the URI of the Web application's authorization endpoint.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/oauth/2.0\"\n ],\n \"id\": \"https:/followrec.example/apps/myapp\",\n \"name\": \"Follow Recommender\",\n \"type\": \"Service\",\n \"icon\": {\n \"type\": \"Image\",\n \"url\": \"http://followrec.example/followrec.png\",\n \"width\": 256,\n \"height\": 256\n },\n \"summaryMap\": {\n \"en\": \"Follow Recommender is a service that recommends people to follow based on your existing community.\"\n },\n \"attributedTo\": {\n \"name\": \"Alyssa P. Hacker\",\n \"id\": \"https://hackers.example/alyssa\",\n \"type\": \"Person\",\n \"icon\": {\n \"type\": \"Image\",\n \"url\": \"https://hackers.example/alyssa/icon.png\",\n \"width\": 256,\n \"height\": 256\n },\n \"summaryMap\": {\n \"en\": \"Alyssa P. Hacker builds cool stuff on the Internet.\"\n }\n },\n \"redirectURI\": \"https://followrec.example/oauth/callback\"\n}\n"},{"location":"fep/d8c2/#mobile-checkin-app","title":"Mobile checkin app","text":"An iOS app uses the ActivityPub API to post location updates for a user. Because the app is a native program, it uses a static site provided by its version control system to host the client object at https://developer.git.example/kfc/client.json.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/oauth/2.0\"\n ],\n \"id\": \"https://developer.git.example/kfc/client.json\",\n \"name\": \"Kentucky Fried Checkin\",\n \"type\": \"Application\",\n \"icon\": {\n \"type\": \"Image\",\n \"url\": \"https://developer.git.example/kfc/icon.png\",\n \"width\": 256,\n \"height\": 256\n },\n \"summaryMap\": {\n \"en\": \"Kentucky Fried Checkin is a mobile app that allows you to post checkins to your ActivityPub timeline.\"\n },\n \"attributedTo\": {\n \"name\": \"MobileCorp\",\n \"id\": \"https://mobilecorp.example/organization\",\n \"type\": \"Organization\",\n \"icon\": {\n \"type\": \"Image\",\n \"url\": \"https://mobilecorp.example/organization/logo.png\",\n \"width\": 256,\n \"height\": 256\n },\n \"summaryMap\": {\n \"en\": \"MobileCorp provides cool apps supporting the social web.\"\n }\n },\n \"redirectURI\": \"checkin:oauth/callback\"\n}\n Note that the redirectURI property is a custom URI scheme for the mobile app.
redirect_uri parameter after authorization is complete. This can be used as an attack to treat the authorization server as an open redirector. An app using an OAuth 2.0 authorization endpoint as an open redirector could change the client description document to have a different redirectURI for each request. One mitigation would be to archive the redirectURI value for each client, and cancel the flow if the value has changed too often.client_id parameter to avoid attacks such as very large responses, responses that take a long time to generate, or responses with poorly-formatted content.name or icon. An attacker could use the name, icon, or publisher of a popular application to trick users into authorizing the attacker's application. Tools such as shared blocklists, reputation systems, and user education can mitigate this risk.The following authorization server metadata value is defined by this specification and registered in the IANA \"OAuth Authorization Server Metadata\" registry established in OAuth 2.0 Authorization Server Metadata RFC8414.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/d8c8/","title":"FEP-d8c8: BitTorrentTorrent Objects","text":"Authors Status Date received Tracking issue Discussions Repository Jonny Saunders j@nny.fyi DRAFT 2025-11-03 #722 Discussions codeberg"},{"location":"fep/d8c8/#summary","title":"Summary","text":"The BitTorrent protocol is a p2p protocol for distributing data described as a series of hashes and file metadata contained in .torrent files. This FEP describes a JSON-LD representation of .torrent files as an extension of an ActivityStreams Object.
A torrent file1 is an abbreviated, verifiable description of a file or directory that, at minimum, contains
piece length that allow downloaded data to be validated against the hashesA torrent consists of one outer \"metainfo\" dictionary, and an inner \"info\" dictionary (or, informally an \"infodict\"). A SHA1 (in the case of v1 torrents) or SHA256 (for v2 torrents) digest of the bencoded infodict serves as the content address for the data summarized by the torrent, or the \"infohash\".
Torrents are an open world specification: the specification sets the minimum required keys and their use, but any additional keys are allowed, ignored if the consuming client doesn't understand them. Torrents are encoded in a bittorrent-specific ASCII-based encoding, \"bencoding,\" whose strings, integers, lists, and dictionaries can be represented as a subset of JSON. Together, this means that with minimal adaptation, torrents can be represented as JSON-LD, with protocol extensions annotated with vocabularies imported by the @context.
MUST, MAY, and SHOULD used in the RFC-2119 sense where they appear in CAPITAL LETTERS.
CURIE prefixes used in this document: - bt: https://w3id.org/fep/d8c8# - the namespace created by this FEP (see FEP-888d), referring to the attached JSON-LD context fep-d8c8.jsonld - as: https://www.w3.org/ns/activitystreams - the ActivityStreams vocabulary
A Torrent object is an extension of an ActivityStreams Object whose \"type\" is \"Torrent\" and whose uri is bt:Torrent (or, expanded, https://w3id.org/fep/d8c8#Torrent).
The contents of a Torrent object are, at minimum, those specified by BEP 0003 (bittorrent v1) or BEP 0052 (bittorrent v2)2.
Torrent objects MUST be JSON encoded, mapping bencoded strings, integers, lists, and dictionaries to their counterparts in JSON.
pieces root in v2 file trees MUST be the same as the encoding used for keys in the piece layers dict.Examples of strings that should be encoded as base64: - the value of the pieces string in the v1 info dict, even when there is only one piece hash. - the value of of the piece hashes within a v2 piece layers dictionary, even when there is only one piece hash.
Examples of strings that should be encoded as hexadecimal: - v1 and v2 infohashes (below) - keys in the v2 piece layers dict, and values of pieces root within file tree
Additional terms may be added to the metainfo and info dictionaries, and when returning to the bencoded form these terms MUST be kept in their unexpanded form (i.e. not expanded to full URIs if they are terms from the JSON-LD Context) EXCEPT for the terms specified in this FEP, which MUST be removed from the bencoded form.
When bencoding a Torrent object, the inverse encoding to that described above MUST be applied to strings: hexadecimal, base64, and unicode strings MUST all be decoded to binary.
Torrent objects MUST contain their infohashes using the following keys, depending on whether they are v1, v2, or hybrid torrents:
bt:infohash_v1: v1-only and hybrid torrents MUST have a hexadecimal SHA1 hash of the bencoded info dictbt:infohash_v2: v2-only and hybrid torrents MUST have a hexadecimal SHA256 hash of the bencoded info dictSoftware that consumes Torrent objects SHOULD verify that the infohashes provided in the Torrent object match those computed by the bencoded form of the Torrent's info dict, and MAY choose to not process Torrent objects with incorrect infohashes.
Torrent objects MAY contain the following keys:
bt:bencoded: a URI to the bencoded form of the torrent.bt:magnet: A magnet URI that MUST contain the same information as the Torrent object, subset to the fields that have corresponding keys in the magnet URI scheme.In Collections or other circumstances where presenting the full, materialized Torrent object might be expensive, implementing software MAY present the object in an abbreviated form consisting of
Required: - the object's id uri - its infohash(es): bt:infohash_v1, bt:infohash_v2, if applicable.
Optional: - bt:bencoded: a URI to the bencoded form of the torrent. The linked bencoded torrent MUST be equivalent to a bencoded version of the JSON Torrent object (see Bencoding, above) - bt:magnet: a magnet URI for the torrent.
In circumstances where other ActivityStreams Objects might be presented as a bare URI, Torrent objects SHOULD be presented as this abbreviated form, allowing the possibility of using the infohash to dereference the torrent contents via DHT or other means.
For some example torrent with arbitrary contents, we might expect the JSON Torrent to look like this (assuming the @context is supplied in some outer scope):
{\n \"announce\": \"udp://tracker.example.com:6969\",\n \"announce-list\": [\n [\n \"https://example.com/announce.php\"\n ]\n ],\n \"created by\": \"Example Torrent Creator\",\n \"creation date\": 1724037213,\n \"id\": \"https://example.com/torrents/torrent123\",\n \"info\": {\n \"files\": [\n {\n \"length\": 5261174,\n \"path\": [\n \"tentacoli-15-Tentacles (Versione 2).mp3\"\n ]\n },\n {\n \"length\": 2778997,\n \"path\": [\n \"tentacoli-02-She'll Never Come Back.mp3\"\n ]\n },\n {\n \"length\": 2333412,\n \"path\": [\n \"tentacoli-03-My Son's Friend Is A Champion Pisser.mp3\"\n ]\n },\n {\n \"length\": 2562622,\n \"path\": [\n \"tentacoli-04-Summer And Winter.mp3\"\n ]\n },\n {\n \"length\": 2796486,\n \"path\": [\n \"tentacoli-05-San Diego, Yellow Cab.mp3\"\n ]\n },\n {\n \"length\": 3863849,\n \"path\": [\n \"tentacoli-06-Happiness Is Having Two Killer Whales As Friends.mp3\"\n ]\n },\n {\n \"length\": 3696387,\n \"path\": [\n \"tentacoli-07-Too Risky A Day For A Regatta.mp3\"\n ]\n },\n {\n \"length\": 2583746,\n \"path\": [\n \"tentacoli-08-Sorry, I Have To Go.mp3\"\n ]\n },\n {\n \"length\": 1924024,\n \"path\": [\n \"tentacoli-09-Scotch For Two.mp3\"\n ]\n },\n {\n \"length\": 2463949,\n \"path\": [\n \"tentacoli-10-The Killer Whales' Games.mp3\"\n ]\n },\n {\n \"length\": 1783901,\n \"path\": [\n \"tentacoli-11-The Capture Of The Giant Octopus.mp3\"\n ]\n },\n {\n \"length\": 2965602,\n \"path\": [\n \"tentacoli-12-Two Old Kids.mp3\"\n ]\n },\n {\n \"length\": 4319196,\n \"path\": [\n \"tentacoli-13-Tentacles.mp3\"\n ]\n },\n {\n \"length\": 1555839,\n \"path\": [\n \"tentacoli-14-My Son's Friend Is A Champion Pisser (Versione 2).mp3\"\n ]\n },\n {\n \"length\": 3177585,\n \"path\": [\n \"tentacoli-01-Small Town Pleasures.mp3\"\n ]\n },\n {\n \"length\": 2877921,\n \"path\": [\n \"tentacoli-16-San Diego, Yellow Cab (Versione 2).mp3\"\n ]\n },\n {\n \"length\": 1079679,\n \"path\": [\n \"tentacoli-17-My Son's Friend Is A Champion Pisser (Versione 3).mp3\"\n ]\n },\n {\n \"length\": 874993,\n \"path\": [\n \"tentacoli-18-Too Risky A Day For A Regatta (Versione 2).mp3\"\n ]\n },\n {\n \"length\": 3951350,\n \"path\": [\n \"tentacoli-19-Tentacles (Versione 3).mp3\"\n ]\n },\n {\n \"length\": 4358356,\n \"path\": [\n \"tentacoli-20-Sails.mp3\"\n ]\n },\n {\n \"length\": 2870766,\n \"path\": [\n \"tentacoli-21-Sails (Versione 2).mp3\"\n ]\n }\n ],\n \"name\": \"tentacles-tentacoli-1977-ost-soundtrack\",\n \"piece length\": 131072,\n \"pieces\": \"KHWQgYXgbUMnxhVsSiMXnmT8XKpeHwMOnVFCk+yZuDQegJoJy+JuxOxmxOLM3Ah0uGR5OsGgt3UmkZFMCCUNZkfa/MwHc5WE+PMedjx7sbNcNq8aBOEt4+m4qsC4WXfU+21w58CpXdIw59MVvhTpvfWSgE/zfGjEcQpMe7bpWgP5cBIVRo2uF8TyU7GhgHxmJf2dAGeNb5qxsNiEFr/aLmFc9S9+ImE+DFBf2xVfWommHSE+RiLUKoR+cGlFsuRd8nI3TptBqYhI/u+2XWE1NI//egRcAGPewa9rMQ+MYPtcBQdAxndhysx9eL4AB6vPalSaYiFrJoSAJ/tcR8HRNGTu2HVb4Z71gy/+vo8kJDJBXZA+YN/1Al1F0CXucst3Ksh5aCpOnFyJfnfWJh6LZCZtaq42eVmoco6p9U1YYJGPSnwIWxnZXgikExWlC2O/lGUUdTzLBMIzbGxZLzknJ720C5n7zfac7zIoHIY10ga4yjemeg9D/4J+NUIrTVc34J35sgniluu98Lk+VpUKJ+WCKWI47Ttv0rJ3sDobiHSkApRYt/eRmrr4wo8IVFnyWmL1HVT+Lwdl5zKnjv3m/iqmPgytNAlZIagzwAJYFFQBaE+7TcKVY65Qw3TAEEMbALv1/ukB99sClOK3FVY1IxzTJPEnBUHjq7grZUpYb0elaT0Li8fw8fpcJY9Guctw2KcZ0cXnDCZb/8FzxWW/s08uNyzAbphnHi1F5XznhsN4GcieykZD72fI3Bln7YlvtDcHipabxGOX3c+WzHSIK5+YNDKqLLhH9hEJ7orw+ViSyWABEgI4esSogymT/mhKUIhhFhRY8OHWM/4d7n04dvfuGs6NawkMn8mDK5XnqcdVxZgGmF8b6+DDTkqQMpn1t5f8spa1NhHglxqPkfVvqqLs8aEOrGE3VfBmqmaRCngIGQTlAIVo3hcEd7t24896xifWf8AQLiqfKbAJXRAPxQG4dRM49YW5aUeaWbv8vt10WlBsfVBVTL2yo+B9ePWvlj2QW41a84v3VorndZS9x34RVNKqLRAYmXt54u3ZHu4BtaHjZovhbGTTPclAF4N5vQ3ymf7Q0UCyagp9FOwLSmj7d64SvoacPN8Sp4fy8Ylxn69ns+df3wkhwAqHwYeY0ys3JeEER4paBuMyetcX8IRYQMc3zfHYBi+IA6Xp87Ey56I8p9e7WVGyb/kt2krhpaN34A6UgXIepU9iJ0npY/mzKpt7uMv250EnoaFXYscdG4yLqp2Q3sGpJOeuS/csBCiqYPR71lBQACXRpAthXNcxh9Yu36jbfdQOxNgmtVlsx14/TtjvhVJUzPXpjFDgds82QBGw6xPhTSZLm50diF3X055xbvl2aEdqZgO4DmFV0C64lkQ9wYg7Kgb1QpeE18k0SPaYP7D8TONpq6o6rl5f8ug+I6Kx2vlZxEx53dyXTg0BYv8a6j40xdMR3IonlBLlkrx9BvznuR1qmgFJ5bgGRsky8qNx7RP+HYZpzYkPMPe8HykGOHdN/o7I81HlWiPzj0u5lkEIoaY9V6YAXcDNGLAR1X0BF0MtR6sKUe3FK8NbVVStVECcwz9fQO7bO48OuWxNDJZjK6qeZ6CmwZUc2hZkfLAmN8DmrRCbQUuYdXblQvEnIXYjeyB1kL0cigIN0eO7uSVep8Tkr7OPr6ZadZqtHLfp8+743Y6GhCfqq4dlcu30kPUwOqJc4ZnF8dIVPb336lq2RVLARYJyBD0Ut8gQQLmTBYoNVxNhI/ygkDBRjbK6TCkO6lKNuVkJitdiRZO7KNvKmHzV1sXnS6BYOAvfmyaLm5MG4sxKQtLww0mmvh77fpIAFuZDY5nqYHyDYxOoeoL0GCUgYJPw/7lNj6KGMozdW7ewAoyUp3/n5n9WS4sLx8hxj+tUf7jN9oqrgPZJb/IlkLhqa48hJTmefbcSxCNqnZrld01Lua0qmclvPESm3IOn8ZDwcZ3qTL46nt2zCm1ovuWMBPS5RLuVBGYelfgvEnUac53P13/TiN9dZXHlMOsdzkqwi8Q8npZoaZvvjTOPTXYQbH8nA8z5e7yGDAL8PFsLOakKUOMZF3LxiHl3Zqefg1yYgnMtVjvER9T2dQVmxVolRokdZx9un0tQJL2h/fCAIIpAGClsjDOv4rzxEswAXLM265U8g9LDWK0o9+dlhLB/HvDBTqK36eDjQXpqOGU4R+8GEm4SVN0Ws0MCXQoYdkdoFcddwptuyKoywdfw+UlGy4aOXZjuMmHetS457CAQnQTjx3YEvtOJ5VsMlj8UndFnOXtOdTABLe4gUjtPgO83KLOg6Zm7JsIw/Vo2IbgX9R/uxeyqDA56ZV43V1KKdFV6xUgU+pcFQQwBTiV6WtF7jQ472NgSJULEhrl4CltpBJ4m/+tjQ06tom146skdaeL8xkfrjPqm74gYlKOqRIsiWa04Yiytw110DIOnvW/SRCnu99hdCkzIE3quV1boXsmDkiYPBUHUIQeEJU6a+qxXozGY+ETe5udc287NJr1byeKRPjg5DYbpgb7xOsBZzj1D656iQeteICu0Ylrmx4XJbsT/7y0VjCMi6bwGUcnAZ/mNZDPKJPcQBReOCdnXp7716tYw2VxTKQpHWWkxQkCFtgH0z8EHuyjZscZD5O48i9qrhCMs21vVHifLHphVSKRX2PhdL4l943JEg5O9EkGnI+Us1rD2voISp+f3VSFkw4owBM/yuUenUU6IN9sdgpc/HHeIAqEFrBtozkyWQIhS4kUeSLmRsmM3e6wAIztCLSb/u9jSir9qctk/xVbAj4sf9Cdc2LZM/mMzg8x4wTbrZK6B/OByTR/SyV89ZAFWxFsPbm+hWZx82B47Vhp6fU480/C38aaUCItSQAtT08VBF51kXQ1BDZ8cqsmbf4ErbBYi+rPXhSf5nO/PuCg6ZJmfOeDZW8aRw25mdzoKpNVXbDU64BlFlNrrgc4y/jYT467jU2tzxrt1kBplE376xocagNK3HU8SEPCC1wFEOthZiVOM91aCdkt4QznjENfZqxARiKT+EddA/khZ+P79HU9O7Tsd73/z21JXsqAkF/t4ZhTkIxDxGumwuHMuOxiCej0eZN6jZUWsXo9dqueGgjl/odcqMXq5WyXVZHyZUsU0/1vXUPUlsnMoIq53DqXa7jqjHCylFOxAVJHtTarJzvQwCD+cnzBlSlQRd1FOgWBIajwuLvWpx5rWCt497Bp+Rn4z31xMhaD/nSsbGJFIAcV1YtUAG0TtR6DXFVLYNh9ClN+qaACAxLV1yYd2t0XPw1/HflEXwImPeRrJJk6gCE1mib6lMrawqirF4tD0vVbGotRWb4oHIaDS7GTlVFPpDvKopi1TMqBsPF8go4nwMg69saPTm4U+/KSR6myU8RSAc+fyMdqTPy2A3Yv8RMp7UEEWFXEcC62PQnDdKTzhqXTFSjybG7EflrsFbeUmqeW0LShCDiZuvWPX07I/NfM7sKhoX36J4T25Q7fMi9VZ2j11Muf7SG6hqFcgvICtfOzXpU5w9WnIgSfaGLXoaC4NR4dExSSPoFW0UyluTcFPNsAN6Mp+dzwOuhAV1rS2XYLW7wjFutpW2pUaEBW3YVR0AC6zWsDuHCQIeAAPUQcaTLgVReTIiA4dAqo1J/fh1/xofGJlf8Jt8UK0/3Hxs3cfTAguSqIGRbxm+zzty1xeqIkDIXUbwxEdFb8+3YiftPn1zA2BTo+gl6NnGjE1WTMctmeb3AttiH8QvQA8taGESNJAJebioxm3zHpHywG8IGFDMI7r5BcHxDKGNAdGisYdXF2YqpZPATfUvUspU+em6wK1RM7eeo03LnTnI0wzpChk8eMDTAIqHy9nfjgSnxUTuK+8CN+6bYFkLxBf+u+q5BIVr5HYStlXuOAADvuuqcOFsH5myf3C8Ma0l4j3dkP2MuiSGExjOFrwB5Fzo8mlJ9zr/bjVzcH6zXk4tJjzb2onaDZItWEi3NkcICF97EHpgFpiknSL/M+C+qq/i6lXs/KCEOuYhipw36uerG4VAidCZOlGtlr3rOHUhycHDBB6zd+5lu/f7QHKR6H0w1kGTiQ02E4y6o/bElg6pxwpZWD9daKHqBhEO4tOm0ca7R2XlbOqVviUN8tXCj09fQ7Ojrv42ClFjz0utxXmG59IvNUtViNTw879Czh8pKMAbQIzrzdHkjZd4U6jF7vVGHCFyqxWVrN1EzP9kDTM3J3QLsAHDIFs/h0IJsHDEsYvSWwgqonIuw+mF1Xnj0hqgKz55v7cH5FRVBjLTAzKJrh6uQTIf5A44k+KXgB/4hlDSd8o8tkTcOAKQgRWTvdf/uwnoH95FjBhqoJo2nZ7kxZqvuC5XKVFw7pndFClCbnyZ+XbLzitYwHYn/3CDXamfaCUXp4QvdISPRaWrnLd49IqGr5i3mYeW+mQ+YaUvQaNpF4eL2Fe4K9+QojrzjLvmDRmQFlSdNdrCUw8DsZg6DOGLbkW3CX/Ov9XMugrIZPoL2z07yffOgHwOV8+JPrHEBnqmRZb9dyb81/TU9C0ItWMuXbS/4e/AFZHK1LGMTRuJIWY68oxE0Xic4lYE8fsKV4zGKjJbjkt17hqBEBuLAkkKBtrYLrglWmThuJHqLjaQb7VfjedjNFb5Og/UkbSX1G+Oi5B9c0O34rD30p7eVXE6IFj97Kme5BduKCK0QAXtFOkSnjzhr3VMkjgdBW66Cw/SwljLhTYg5mC+e5Ws3vVwH7t1rfGnS/JL4bkgCVNcW0xsIniUxjjoD0Hj9LENMmeXBFMv6KIdD4eLVMCC4MvaEQZtwmt5V9V0/Qia/FVj90cjIYKegBP1fPBtksQzi4LC+/DpPd3cjkvdPFXZ4Votk8Begx//f1L1RVQX/FGvfJTb+LoXFrM031Mab/hNwP7Hr3gRpXZNam7XQ73dOsv5lx9ziAAr/SNufkcgy80b/rTmpBJX4VNZAESGe5IoaduSZ9KXkt7VkhrlD3mRQigOz6g8XiPjFspwB2P6AooBxZMp/5Wbi7JNbXBj+l6ZXQx6ZhPbVCdlgeNkl6+kSrz6KZTZqVnamCteJP4YJwZxpQOALFvUIK/CUA1Hx/oD7rHIuyX7kTEpltdksUhmYRNQ0dy4b64imhC1fL/3HSPpnVH1FopJ7GRVNNeNMnb3h7YJCZtSqXABADSnxYUWaMtQjFPBDw6ks411fAkQnGFeitFLFhmO/+2uwXb2DfXCkciCLK+ZEUadhBKbV4Ptu/pC+rg9hKRNKaF/BlHXR43cj8iYd+HhRz5mMe5vBXt/LOUr9xOREcNFbKb42IzHot4EHq3/VKQs2uKW44+nlJDys0k3foSTso7UA3ZTHieFKqNwgoj/rkZQZsMZF3byF9Wl2CeP5zS50x1oUHRUyqflAOxzyT/R54n1B1ATcE9yxFy1ZAIggnvBjLK4+8QcsZUNpF4DGKaQbpUqY/Ex3URhmyANQysVrtehmCJ1jr0ZkJV7eJ56SaxmYe1VPw2dbX/4LjjBThtDMOQGqPc5ct3MH/t2no+t+IfJXOcbdV8nzrCYh1jo/HADSmJCo/rH3dwTk2HvpB9KnFjfFLdwhfpvOVjqxyj26vYul0ucS9U9Or8LbUSIfi9SoPIV97G2Z4jwCctZmMHDLz7/9N90GSjmYkiiFYxJ9NV7Zy0IK200RT5+ywmnTHqQBqbB0NZcYLHvq6fBVOHuHAaLgYjzPkXyajReJsn/pJcZmXrR+1JypmvEevbPJtYDPoIomhbJOI4q+DDwieXpx35pyT+8BygkCwvZlOUnMNI7DgamGj6zpkqLBICa2ItJMjRnaHCnbXR4bvJ5gyVhOT+9PFNecjAClYyERpDh1ydFMlHNjmhBEqHyz3Uzb/YlrG5OEzLjz1qf95/iMAzfbiOhjzorbtmCmEgl7IopWvqHmEj4b9Z/47jrHn3PeaZPbHihsq4pGnyr51NCPl16OTSTBA4a5u+g3m8rnL+CMLQ72cSTNLLEK3V57KIeaIMpzJg0ZYb0rSzNndGPAKdS5i3vC9hseaynsftvaUz8AyViUtWKVgU8r34cD5/yYynumy5OisB0M7McthNIQfG3wTI9+OgM1wFB3B7+M8Xi7ttziUfGKjqIwo8PF/F5FcR5jCDxZsQYQ+1hMqVeNR2nCUnAupjMvxF7KJwwC4NNbYgphjDBz6o3FITLwp2PTqoQqOJ+lJpUl5vb5EXFNfsAUHYrp7dAwjVYn8y3otaMj2ont854tXK84GWtCe6BkWQ7kmp0K3PCvcMa3ghFmLoLr5Xk3RcxNtPXt/L6rFja3olrH6S8mNbtprc2WK/sBfI0XDKp0U/pqKd9E1Rt0X++DEvSBNucpn98/phsFEo6X2N+L0e+QyncDbA2RaDk7yUiC3Fm5xwJSNSS5L4zjth7CymUHVapZAvRgt447kta/KOc5hfEooL/axRzfcBmf/6APQ8Ot72+DHFyZtpIS7bajEnC3LJRguMoJnS32X1tuVOgeSOys6bvamq8C8EiACnoBDlT+rmIeIAPLRhFtEYYQDy/5ze+BfhH3evAAzkemPMEYD5hgV3Dy/HOrXDEV7mHt+2bJ2yjwrY0EfbEZmUY9T+bFLKMLm0DPRxl2aWsKLF1vUOHsqGctPp7hQqaN9zbKDIjpPf8ZgwAFSN7L3Nam6rCCc38VPogDeLlRB6YNB/anWhvUkMc/aB9nEwf7bc4j2FxCpqjqUP7BIhG/rsk3FBOBTe/uKk/TC6XOoekUmztfLpM64J9q8r71VVmuJ4IA0O/f4lw8tSyKJiwBbgeh87a+9Uo0FO3vJVVzH8bMlxQKcNAG960fXB40WMrEB/YOA6bbhUEjZVc/bqHCYzf4izoPMqJZssT5Zsy6r2aQIAOFRyXtASG3nhD6urIobLUhOER5VveS79qQI69zUMsZfLF2Gv32Me/NZv8pEoHIqQAlf6NAyCvV2WtlIcYde/JNzLgE4NQwJmEgX3b8zuK+WQHxCMIHqSvDPTxdIGrNn/f7G5wBbU6W06yHNAEIehFhUJHE2OxxcEppE3NBJgML+X/DE8PqOY6eQiWBbS4yGvaeF7gvhuidt4djaeV+8STxL+/B9pfv0Sf/BkooAM+igWPvmDGQZYahM0hyyO2Urks5rHC8HeOnAa/qaYnELVY92qWmOvAZNmlYFIbAM1zj2yZVDkcBeNy72c2IAlRGJMmBxuUJTGrnOY4UiRBsiYVM7n8Nm8FUwIxAv/PjICGS/kJ/21CaW4jH3nkycD7MkbgY/b+cu9DxUmE6dp/6JclZ9ziu4e6LgjfAF+qkmRJ8dIkvrUBZpKhHQt48pj/eK8cgBbNObIuKonClHVtcQXTTl9/RFuXS33nGrP+EN8M0qfgYm4ObCKpA47TA9I4xCCBKbyUMbc80JDLuJ46wXEncUVwxoJlRxHzPRybBAp58RT2t6AFFTbQsWguFDE4h6Jz5ZXQxJ0KAU2smWCpVxNuj1cruqPU2Bg9FLK+PUprcAwKKrrZ99N/5LEJc4iM6lLic7s0KJtYnHevJKqRrK6SsNXDTdWKzsOjjWoTMTFcO6CaJHbWgzgbrjEMX5yznG+c6epxIEe4PXDoKc96hlw9Quk8pO+s7bwCuiJOdHULK0a95hnwik9FpOJ20lLPOBcjtUEs6QBbbbJNiggg+N65uP9nIKz2jZvNNkfihuBrq9lmldDM/zrDhS++rGI83Hk8uNd62E42y2oRzvXksYU9zZLfj6r+WgGKXNE2zdSrppM7seqEvCzFrYTh9VF4d0mcP8fUP2NjQaSouzYvcgu4njLNgf9NI5YGAv15n169Qn2j5jDN1GCsUItCW66Z246qOyZadtFWv5OJhI/yfU/6nQ+rBG/YJj2MbXxZtYqUiC8HFdzuIsMjFhL0uNHuVS1JYVkcPkZ3UKMB4uE440tF/TTV5cPdjGDdBXgfzoo7um+hU/EN3Yh0VDUhMDVhmwXuTw5QjKMu8QBRntbY33QsFtqNUWM5JYsTmZOQoonm2gh7gkEc8mPo24DLoRKp1RyBA40d4PncSA7e/usX3LRUE3WvNCF8A03je7PKEHpIPuwBaDt81NsvnX3uoZr9MQBtDBw4/D0x6OwH/lXtnHb/u4HAL1AEuXHqCpguTWXOZdoPxuiZLjRHEHJMBfCBccQfitxQLridrHMr+cKskYzlvbFDToFjQVUGgMZdE9Yl69f3NHBunV4Xzrzn9z5uFa8iqOt5oyqpZSTC6iim7QkmDis56gksc7ebRN5ASr2mNg2/f0zblkJvohbM8Us2m4lJC9MAnryMxanR1xJg6CDD7Jmbd8pqBHc9mQ9DyC3XU2ZvN25+tqdPVy96cmZ9Ii3p7P6Hp7c1gDenDze1QSTI1JuDsZfI2r0OC7Ot+LEnd0GmmRRXhfXjgkdmWqGhwxfBp2FVJ1vfXyxA3+MdXsowpUKOHga7+I7YKtHzQvR8bo2CfbXiriqqxDmoRR8+gVlqVl3JBUIZX2gtSEoUyz6KjlyaUmzv6UOOz27cFc7tiY6zJ7i8hNdbAZRAt3/ge0i+1f0vNXXfW3Ur0xPlurhBWw58jf+S/lCVhqTbdzk1gTCSX0YNGYbXSAOe2rWbbQ9jxXxt/fSHVHYfa94P83yF8+fCd7KdDe94VeeQqg1fwTcDu+6A6udWrrgey1SnwqushIZfXFR66RSmNEijZp4lFIzkvdRpBzRFlLhTXpLURjCE3ndnNbdYhMqupllFjareSAW4hAwgYVbM6PGXweMh9UAuQP7B6z0Hl/hWDExb8ks71aWxRd94F9NOxP7pfDPjBWyTgBREgdJZ7PBFzl5SoXbOYZ1pBuJYEavd+kpYi7vw9scf2CIJQdgWXS+Itq9agLJhwBcoGQ8bpdzK2Ckc4gT6ZCASu5Zvxx9Yp4KyNzY5+aUmMbm8tHysTCsB7958FO/gZNrbpHSfigRNfN8P3aP8JZAOD0M2nk8rIeibC4kVtwRitMAxr/hk2jtSN9MUKEJDcRcljU8nfo9gV8XEg+OgyxF6Q/XTkRn7e8z7Qxj56oH2KKPi7GVik9KCC1O+9KDVA4JvJG8GeI4KqVGekL5as1zHTpH/o9/SgNUhMGch4eujMWI3gnEUnzB0raIJGUBvnL1ChH0SceVN5CV+Tt125pdbWdH8rptKlphXXthSxPt1vUcjydQfIMsWVLvtYaJZagmwpVqdycq2MpiF6aQIkGWeOoJ8gudHiTB2YbzGu3yZEXUV7JTfAW2rTR58ghg6ywruB8O9bjBKAOmHi5vf5sT5sv5pN88NLACYbkyp24DbRT7krw/AP74mhgxgrXIQ4j5Sa8fp4D5IFJcx1EUe/xYTGLtKaJFn3U82MkHGIVvuA7NHa5zockqNiWxf9YTSOKhhvdKa4VmpOS2tEmwfEJk5wqQAO8TKBnzdyR67+8jWmSH6V2WfVXw8PTibu37enPwrYsYwz/SKKOeKaJVWvIznmdBSFVJTyqT7pjDN5a+6LYneVjz3cv+BonlUuktHbcpDcKVXak4eU5nKmJ5CM/hCenf57bMtWLbKl5uGUBwDLqmkmxaNzSC/xA555cqfEbbYe2k89f/mLV8dMYO3GTlxKQohrIzo6kn3yP7wCbxGsomyteBSaXVIGTUECwe7Wq0Ls7BQA9wgX8H8Aj94DtR1rERRyOgLQUXkhiFkIM4bc5jg8ksdHKejnOlgbu7rwzg+A1hXymstoDKPvMcLjm1VHb2jnGp3pFWeNsA/FxwktZwG4cXSKVexIGca8CIcaRU6WCFazbkZ/wKLl03ou2TNfuphrruuoknRTLeH8oTgTgz0pSxm3yyhGTfqcgWO33rOWNsOthuJ3y5mTYdRTM05FqFZJr7ipZq/4FPBTbJeFMWIJjKugm9WWjg6JzBJpeUEh3MA9MMGBDDIakXAQfbgv51a3FrSwAxIu5qeftNgTQYjnKJtXsPt3vDBzgPsPRlNili3FUDNCJC/mnJufpPrgpc2fsqPiJ/2D4taVBrqElCmQjDt7VxRAKwvxZjl0NuIl54UODInCvk2kZvhS7yr5IUq53rYXULB9dA6MZEE/yCByIOpEzZgusV0ehLyllBrk/9XtuFuBKcJSn+sfjVLECd4e39YNlp5KHAfUPaEKMasPs5dH+npmHmW/2YGYQETeLE5cka8Yi+/Xahw+vRzUYSenHnJtuUA8a5swzEKW5EVw2ypk1Y91/NtlyO/Fr8jE8gz/yqdQzggrd4/s5KoP9S1sFgwhE+VfT+GcLUbjB8CLVCJymitZXJgGpOcfeFVxQd1cLG0DyWtcpDu8PsrzHKclY02mEATyFrefUSLHwJwoQ8Vr2gcXA33YmKOkdAC+vhilRW1Red6HBs79YQSfQuOgapQq3ZR84v1Um8beZ6G6Wkk6tPv2EqwyF6fDbefRTW/ntY2mUQaxmPUvTGNZQWTYt/AR2tucFDw7y8vN7oVONUKYeoVPcvl55Lc+Rd613Gdc2GyJUpwdKLjlkET7MmC4sX7qpU83WTrhnllwuvd83nYme5PLwqmLXdgxULkq2hWBTtdK5KHaNkSOkGBaN9rv8GOHsQWfkTcbuNfwg6T2MEdP77i7VVRrTiLr+etu1NEOuNh4x92n1aYwsLOhHaawcE9QwdDDZVF14rTBruI5jlWwSQg8oP0rlBiRTzbmpL1/QDPmOnXCDp6kxlLcAUGx1pk1qwBu60ePwvxSqhBvsDvplbZED3AHicX6CaHvAk0gicMn81CAIo8cSdBX906qM+ro7rKy4JIYmBgx2UMzqx+uO4gw8Ci1NBpVkSvHRCzh0rQy9431RN8rm4xNRsLwyJsh7AgW78divbbhjTZNyzBGJsMbED7mq/8JLx1ygcmi9AJjYKoGo1dFn6K8UF2fibLeZ+pStJFOSTtj3WfUAdpjOQ1pNVZ5XNxr+G+pW8YOohY4WXZbHFVgmPJeJKhPVFTpq7czskpx6TrK2iLeeX+TQlqwlPZDIt6nv3sQomHJqZ6ayDmETEepv6bBEvq4CnhTYj9YIJO22A1///QmUCMLwsa5fwXKtAFqjBUx7xF48R3niNkFFKIDK9g8UeepNUziMqE/iZL8NZSeMUHdHB5T65o9zWZmZv23MS5TR0D9LUdqT0hQYU0n0W3AL4fgKy2eS4AApVzE/B1Xva/g+cASlVvpJbek2/ATNvyo/pVnwurqidIcvvl1/+jp6x5caZ2G0g2a6yy4zAX2lHFox8P0GRTPFqi5uTfOlzUHgQ8HQuW8bUVKaojUv3TfJEAaGHNBimAsi6ZPToaLAPCgs5CJiYEPMdVuJCQj5cpo0WiLgxJ6jyRBqa3RRn6F56bGpatNt6lY25G9PnNrLJvpvm5fdZPTO4P+C/qxfr5OJMQgUWre7RaTIXSrmxUpND+LMgG0MdbTD3PP+JaTbJpnbSop3lLWQKX03LlhnihQHN/aDV/FyfAzocV8IiKoLQ9qUwOZDhqRrSrUGG9rWtJn4Iq5OocKzMkiKiMW4ULKhtF7h10thDuxoxa+j/eLmWPNxVXW59mSn2LE9PsTtNJ4BGU+Bd4f1onHrl3l+RPkV0r3B+Sl7AFcFopY3lOcXBSo+9bCRRLpCMzz7oEM4qk9jOoLkwFNLtcJadgugvOuA5oc0w6ccB9KYtaiuFy9aSeWkvvC53EqUfBTSSdicVbn4fFzdN+MsZL+VuRW+FCg8Tv56b5CtNpbOqqRJZ7vy8MATlfFlRbB2Ob66CP4Okx+I3+Cp2TcQ7vWi5Qfsy6dP+/EVSlWg5blfR7qdhb6WbuhwSn5bUXGp+IlaPQfl/k/Dhix5f4U711APLhYhIchQ6GY+yjyUDnXwrWvNv2KUjlt9bFCtdUoeAzG5N3oCF/IBgINbXuubWJpZxJX0MZnSQ+p0LuW+3rjEx6DxHAPjGMFRV08hRYSfo8x3fmvO95JuOZ9ahTz/btV82nvPIfG2mzhg4uqhSkSwMVLkAu+/LYvNv+xZjln39wYRaCfFjc5Ebj5ST8yDhWJprmVrsqVu+ZSpiTM/o5J9jScYi7+PYGgjf9hQg7qdeL1rWjwRkVwM2E+1XrU0TGdCyTmP5lx8sylD4+yFbOgNaE/Wlo8f6fe+EpnLlb8VZ9SJt1HPZTV8hPQKs7kkX2hxmmhlAh4Eo9QMzPSy3eJ1cP9W2frrHB0McjM+1DiO1/hCjsMBF2aSEhWvLxgvKwRFnn2Q3o6rF0lOWo3zriVwG0eXdUX9vAsTf\"\n },\n \"infohash_v1\": \"1ad02871c78eb1c2934f46de0c7ffd9ef9ee4083\",\n \"type\": \"Torrent\"\n}\n"},{"location":"fep/d8c8/#v2","title":"v2","text":"Expand/collapse v2 example {\n \"announce\": \"udp://tracker.example.com:6969\",\n \"announce-list\": [\n [\n \"https://example.com/announce.php\"\n ]\n ],\n \"created by\": \"Example Torrent Creator\",\n \"id\": \"https://example.com/torrents/torrent123\",\n \"info\": {\n \"file tree\": {\n \"tentacoli-01-Small Town Pleasures.mp3\": {\n \"\": {\n \"length\": 3177585,\n \"pieces root\": \"e755700c5bcea4905a1a3f900351d8a5564098bc081e6754e379f805728d190a\"\n }\n },\n \"tentacoli-02-She'll Never Come Back.mp3\": {\n \"\": {\n \"length\": 2778997,\n \"pieces root\": \"d1c3d80bf13fd42b3e8582a15a21172d23b80247f3b6ca85ffa97f018db4dfcb\"\n }\n },\n \"tentacoli-03-My Son's Friend Is A Champion Pisser.mp3\": {\n \"\": {\n \"length\": 2333412,\n \"pieces root\": \"3b458ec67b680d056b9f48d2920242bd812249cebd1939880340e7d2e136fe0f\"\n }\n },\n \"tentacoli-04-Summer And Winter.mp3\": {\n \"\": {\n \"length\": 2562622,\n \"pieces root\": \"3ff20c6f038c048f27e428499498e1d19488f8f934df6632d5d4d827ccf3f3f2\"\n }\n },\n \"tentacoli-05-San Diego, Yellow Cab.mp3\": {\n \"\": {\n \"length\": 2796486,\n \"pieces root\": \"7ad85e1400e68597e006040d4d9373d2a05d9b2ae13f14dd9939317d33ea2c8b\"\n }\n },\n \"tentacoli-06-Happiness Is Having Two Killer Whales As Friends.mp3\": {\n \"\": {\n \"length\": 3863849,\n \"pieces root\": \"d0063c2d1f1c5e3b5e163383d5f5a63ac983be59708e0d695870442cebbe37d9\"\n }\n },\n \"tentacoli-07-Too Risky A Day For A Regatta.mp3\": {\n \"\": {\n \"length\": 3696387,\n \"pieces root\": \"4767dc7f1b8d2f6aaae276034cb2e2c6c656af56683e148f0e54c15382273245\"\n }\n },\n \"tentacoli-08-Sorry, I Have To Go.mp3\": {\n \"\": {\n \"length\": 2583746,\n \"pieces root\": \"661b7dc0bb901d026370be0b73e87fb6a95479dc85bcdefe61711d8fc5b236f3\"\n }\n },\n \"tentacoli-09-Scotch For Two.mp3\": {\n \"\": {\n \"length\": 1924024,\n \"pieces root\": \"6bdfbd145a64cabfc9b0971f87ae69c93bd1847b0c48351ed971c1fa4e9db97f\"\n }\n },\n \"tentacoli-10-The Killer Whales' Games.mp3\": {\n \"\": {\n \"length\": 2463949,\n \"pieces root\": \"e5273e3a62dee1a16328471a471f3a361eee0de07de68a11b6695c8483a4eb88\"\n }\n },\n \"tentacoli-11-The Capture Of The Giant Octopus.mp3\": {\n \"\": {\n \"length\": 1783901,\n \"pieces root\": \"91b3f6f33d7c2cc4752c3b6e79bafe7ec2d3e03c31ae3790a5c132d1a9b93f84\"\n }\n },\n \"tentacoli-12-Two Old Kids.mp3\": {\n \"\": {\n \"length\": 2965602,\n \"pieces root\": \"0b09f31a4206b8c42530dc7fb489ea52f4db6992b1de8c9e56dd1bf711d64af4\"\n }\n },\n \"tentacoli-13-Tentacles.mp3\": {\n \"\": {\n \"length\": 4319196,\n \"pieces root\": \"26c6744b0b3245ed23691732c22f95a9bf074f32a49ba5c9feece809347e889e\"\n }\n },\n \"tentacoli-14-My Son's Friend Is A Champion Pisser (Versione 2).mp3\": {\n \"\": {\n \"length\": 1555839,\n \"pieces root\": \"736490ac9a57251d5feb02bb8e17d0f29e761993bd604ebece2f66e8cfc24a76\"\n }\n },\n \"tentacoli-15-Tentacles (Versione 2).mp3\": {\n \"\": {\n \"length\": 5261174,\n \"pieces root\": \"4c94d4d2d20631afa6fa4d4287af4cf5c262c00f834c027c9df3f711adedb2db\"\n }\n },\n \"tentacoli-16-San Diego, Yellow Cab (Versione 2).mp3\": {\n \"\": {\n \"length\": 2877921,\n \"pieces root\": \"b9f50e8baac6b38dc2f84e93387b8c5e21c334f54f8772cd5dc819e88c671c6b\"\n }\n },\n \"tentacoli-17-My Son's Friend Is A Champion Pisser (Versione 3).mp3\": {\n \"\": {\n \"length\": 1079679,\n \"pieces root\": \"a2781d81c2dc33dac179bce14390485e47f2129ba98ed4efbd4690daf7229d95\"\n }\n },\n \"tentacoli-18-Too Risky A Day For A Regatta (Versione 2).mp3\": {\n \"\": {\n \"length\": 874993,\n \"pieces root\": \"c67de66bc0c2c41fce411d351d82dff8afe809e5206df6b14d536d4ff4c00dbe\"\n }\n },\n \"tentacoli-19-Tentacles (Versione 3).mp3\": {\n \"\": {\n \"length\": 3951350,\n \"pieces root\": \"b6145263d70c69c630d59dfa07856663b33e5621a62a33d7cd706c3aafdfe2ed\"\n }\n },\n \"tentacoli-20-Sails.mp3\": {\n \"\": {\n \"length\": 4358356,\n \"pieces root\": \"72436d6a0686a9a83d782445224036c71dde61e1824ef5ea230462ad95daa11d\"\n }\n },\n \"tentacoli-21-Sails (Versione 2).mp3\": {\n \"\": {\n \"length\": 2870766,\n \"pieces root\": \"b27df2732173f94fb73fece8d7ca2864d2c027d5186f078d30c01e4310d700e0\"\n }\n }\n },\n \"meta version\": 2,\n \"name\": \"tentacles-tentacoli-1977-ost-soundtrack\",\n \"piece length\": 524288,\n \"similar\": []\n },\n \"piece layers\": {\n \"0b09f31a4206b8c42530dc7fb489ea52f4db6992b1de8c9e56dd1bf711d64af4\": \"VPZdDK988MyI5L2JUEvc3RNizYDkWZBhaxZGOqqjJO6N5c5QVL/+NUNQvc6AyZtNFfD2A/APiNK/AVNMjhT2WjgTQeqMBtmnoLKBs3+F+e94T7NQk5uygqd89lOk+eFSW3zIxeoCgjnxq3QlX3i8ABOK3RDVvzFPnegQvpgYoBH+3TlsWxinQ+JtwGFyTMAFwe52fIRscF5UgcqdlgRyrJu6EzKob+25Ngq/Slv0Y/Ij1mSHLRcx6BRy960JjT8J\",\n \"26c6744b0b3245ed23691732c22f95a9bf074f32a49ba5c9feece809347e889e\": \"nIooZfqJg9f3Wa+Kko4aSSlqdLkymMqh+qvpz640fLgJHa+hFhvvQkGCETr/xzUT2YIBwtWr+/8jGUCpE7dWHoSL8ihQXdoNoOXHYcwy/fPygSMJl+r5IgatqWJOAKSX8zyncD6096iCsazYOgp1ya2CkdxNJPpCAyXVXCl7AoPV/ULP7gKn0G3WcqAE+eXPEmLkdzEBI7KWNXsFfhyl5uPDY0RgzfirDwOeC7e34jPxBqxHcfsLQJ1ORDpnhOO1F/Hh3L4hFfiiFifXwqu/tjs7yOUkmKwhpzvr6O5wvA26p/Igw5ztlqj6wlVGVN/ay5anFZB6wmLCmGNt3M7+W5afOyAk1tvRUpNoOMTAabxT0YMPUDiuP6bJHZPsOnG4\",\n \"3b458ec67b680d056b9f48d2920242bd812249cebd1939880340e7d2e136fe0f\": \"hmopzDsPaqQY8kB0fSAE9korQ/EYlhMNNKYh+Stip0nQeIednxVIigWz7SdHjEAxtrISO8Q7oPicQBePe6P5yH70mQLp20KAj82QuAtpEUXP582Ix5A/QTAlv/ZOCCV9bUeY63aSacDLrVzjyx0cu3O0I18d5lMa7GL8TszAeziVxSJDStgC/mSgsEw2lyjyzchw2XHI39o7x+1GlXHhZA==\",\n \"3ff20c6f038c048f27e428499498e1d19488f8f934df6632d5d4d827ccf3f3f2\": \"V8LPZiW6dYEhkVpy50NA8NQobcjCG7BiwqPR0TzNXe+rfk+pF/p7QCzF+tpKReBi5EPurqWGjO4+dbJ73YOO53yCOoLILCo9iji8YyvkB6RQ9nM4Xn71hPiU1+oJwAQb3cgM6dxz5+ida8bDGLpB7CroBXzQniNAbNUungzhJy2T6O5BwEHBoLJmpTrkcLurMqLmHRmGJN2SMqfcMDhg3g==\",\n \"4767dc7f1b8d2f6aaae276034cb2e2c6c656af56683e148f0e54c15382273245\": \"gAhlHM85lyfo/VNx1txeTrZogY4hwv1y2GQ9PrXIA6zqco3cpn6IIttXkb6GgIkz4PiVmEmC4vlKt/rV+mCr6/hj9o7tmCjolGiDw471NFblaxYcYKu+XfM/coV8GJGtAV+bWjotIAccxZUq+JpcMfVD33C9H0PKjX14NLJwAG02ZRMcu5gU1FEjypDUXi/uGmeb9LquiwDv7c9ZJ+tTqH7/K+w8w45GI3gt8B+ZRa4pPUlDAsS9o3efdLbpwNrwvbIgc/PNpmSwoSkDHtik6bclpq+IKm0oB0eK12ic6/NdOTH7eMZ0+FZ5rtn9ThJ028cHkSAeqHs8v1e+UOjx4w==\",\n \"4c94d4d2d20631afa6fa4d4287af4cf5c262c00f834c027c9df3f711adedb2db\": \"AFW7D3V3l7kOP+FoujXugKWb4HH2pXMOc53bQaUj5m3r7xXsXQSTox1chHUlK8ps+O1qxckSydl7e9pPXWN+j84nVqScDN6uF+LJp58SxqOmi/g7nBBJugwxPaf5HrEOAXynbi+CnHFBQPYnZ7/AlSFhpII4j9/Nnv9WT079YgFCc162lViJR860+Rbf3OaATtVAI5oGFJUAVCV636zAsTXoTMj0lnJne+Hb/KgxlROiWKLBDAL1jQ/0Cz3n3D/Vyw4eAQ0AaP0REXemD/wBFDyDE+5ixFS9+cI1uam4bTqGN4UPily/WRC49VpyAwyT6rpMFjTXc7xGTkMEZpNIgbENH8rZxBWFDoNSzJVRL/XjdOZaDFKe5ALRxeQAzbYh7tKeqdi4OXE0p4jzBd8pC+hNM3yDu8LgHDE1qjOM4egkt9gBtsmnSi2Urif1/Q8maaiy3sEHoph4VdA9D8N8nw==\",\n \"661b7dc0bb901d026370be0b73e87fb6a95479dc85bcdefe61711d8fc5b236f3\": \"aZRzmN/dmNjg+FsfKWMZl0l2siifCKnrMYFM2PA5lOOB8DclDu0sXUM55se9HRMVjOcnfNXxY3+mRIrtTdaT8sDbpzHXp9iGqsYZBGEAj07ZaeJM3jHvOky1o4x2Qro5JHtefe2GFDgq2lJS0ha+JUsWG9a6MdH3c3faaj/X4IVoJCDtF/V8WpvVY8eMmVoEA5L9Zfpc9eiTlCJNP7HwIw==\",\n \"6bdfbd145a64cabfc9b0971f87ae69c93bd1847b0c48351ed971c1fa4e9db97f\": \"sysPGOfDZ8wAr39CdfFK3MzWxiPexd7SkLqWvIySoYD/Gr+n3xJTMyxIZR5LwAjaQwAlCqRpgDx14e0kyWWKQBUPlj3EgcMR2/1vCW6mBHFTqfO2Y/IECE/iSUaFJ3i+zWEQcAklqVfqBloKbLGtfHMAVIRgwY8yoAKB23+gmFk=\",\n \"72436d6a0686a9a83d782445224036c71dde61e1824ef5ea230462ad95daa11d\": \"QiFoWXevBNl/jPCnBtGEtE4VfW9k31yMrX8MrTtJs+2DpNXJXQIWYPGaXA5S1iOnhQ1wBXlKt+m7M6TChTE/5/q2qPpe4hp5iVl28bkvb2pYgVZSgYiqGqistUewLisrhTygHutf8Rm2ChDp4cfJAJ0hRc2hHQpMo9syRwpVUYI2HpVjsS6t6Ymokojaxfdxk+88mJ1bJZcNNoFmK5EaqsQVRvjTenxaklYKPZROeDeiUb64FGCTvv8hF0T1/cNFCOV3kdUjgM4mrnw+gDfJIm5JdID5bsC4DVF7WrOWaAH251lWksPzDyIFcSxV0C7l+FihtXM4U/BhsxlnnfWEVfWZBP7tFb4vMU3WsMRCCbbXCsKpuNa/NYCwro+h1MO/\",\n \"736490ac9a57251d5feb02bb8e17d0f29e761993bd604ebece2f66e8cfc24a76\": \"VwRUXEzJHC3pz7uEnEowpwFHA/6Q4WYdUmMEoA1Fvog09IALuR6E3bAuo6FP9pTrn2bvxPvJSKHVhGyWwbGaJn5arMAK4ChGGuZyoAQwOOdQPwx/A90v92qo5SuqhxFF\",\n \"7ad85e1400e68597e006040d4d9373d2a05d9b2ae13f14dd9939317d33ea2c8b\": \"SCiEVcZOjE1GM0aMNWiBTiS7LMV2iIAVX4puj77Dhz+dsqG/hthLs+d6GD3oB29I0wCBYIPUCSDJmcj0ppAvABDDhc0H1bIpQzhqHQmwtK80T122VAQ2MPKCU9vg+k8wkJSzgWKib8AQJEpdpBpEiyO9DPCzSfzLtqPucgTUNvlatKYAh5c8+o0Nf1ewEkZumL58vocsENGianJDe+kSzUJMwJKxT6BvFlE9ujsZ7fSWDV95sVxqOQMFCVwxuNOD\",\n \"91b3f6f33d7c2cc4752c3b6e79bafe7ec2d3e03c31ae3790a5c132d1a9b93f84\": \"vj2xl+bqofTamS/TSAcwBbsf/E5jBX9C6lgZdswP+/7WNv4Vp15ny5mxi14YqCFG+M1+5NOfUzJw6RiwMLWY9R82q7yrrW4ccnBJMcvhP7f39D6BKMx4mcd/ATr0aEV5KbzkuW6io+4uahSt53fk53UMkGAwS1kGivE17ueD1ng=\",\n \"a2781d81c2dc33dac179bce14390485e47f2129ba98ed4efbd4690daf7229d95\": \"TpFZZj49ysDTd5yoplRz0DfoipqsWnG4ZNeNbZA+8M9rTS1ItmqZnol7sDqbMXZ+qrsXiYEr2pWCeaks/cqPI+1ae1ecX8rtsWyA3ej5aGRNt9zUmpYhDazGrqfok2v2\",\n \"b27df2732173f94fb73fece8d7ca2864d2c027d5186f078d30c01e4310d700e0\": \"jnrUt1kqrGZ1gPKtCYyDocTIoOxB0RtsWA3xEocJiyxaUaWv8jWumQAjW6ZMboJ10JODGebBV7HQssjlElEVdmR6HnePf6Ov+Fu/ZfplQHILfLi63PQYO3Dc8ohdwEz3o+FgJ+JOhQwbynrSItQOT+azQLJ8o41yWOGY72SUdhtNEjhytqWwGQIHFKMJfQeVlvLpVCryU6p84sgxIiYRJ8cUZbHPhz4Y0m3xPbR1tJMaIlqcfjOw0I9sHYCA4suZ\",\n \"b6145263d70c69c630d59dfa07856663b33e5621a62a33d7cd706c3aafdfe2ed\": \"GQLFRpTn3AVBJS/ojGtr9lhfSI/8ewYR0xLlkqg3Cnqx2AVlwr9RwBjKMajHSgcmVvpQpoZ0AejThvZz2JygF5kkjVy1Pe7zJ65GfmIMU0CCKkiAyCWuQt1PdDTd2xWNReKv7MH7rVRse7H6YrIAZSdEnziGr6eUVt9+fxULQm62g4rihawxEW6dZvMh8QL9D+PUFVjrgRBUKvYWqRXgRY0vObRxqCQj1AJDSwvUdMv33b7o95rNOsVEdBu+n+Lt3JA7CxwAXsQrgZt9AmGXQTvyu+uONt+2ihaqtspuGAkBf6/D8RN/sybcWtWCI0lgSyqu2g8cYBEMaxnjgKGSaw==\",\n \"b9f50e8baac6b38dc2f84e93387b8c5e21c334f54f8772cd5dc819e88c671c6b\": \"snFE/oMWtUu01QzaJnhsMG+D5D97pbKW287lAAMvMSwp0FK1aOBcV19dvQte9qvXmws494090AGQ3xxAOntIFQjzJlYhbp8JQC+2r32oFf7lyaowwreV535RdH6/TNcd39O9UI0dAndQ9uDWP9mRpBFfLksTvYFktKnlwqqCiYRAGbdsO5FC1qlJxi/X1CZHG9gjk1FxdhH0WLUehsdQwwxuoXxdQ12HLuowXNBr1JeAAWNcLVAqSezuUkulc/Or\",\n \"c67de66bc0c2c41fce411d351d82dff8afe809e5206df6b14d536d4ff4c00dbe\": \"GaAYwCiYeDqYOop50ZEV4TcybE87ULDDE3Nv1ZG4geIF6cRyFsjN+IAOqiXe6wjRkdReNo3NLKskpp61Ca2rBw==\",\n \"d0063c2d1f1c5e3b5e163383d5f5a63ac983be59708e0d695870442cebbe37d9\": \"fi5NJSOcrOVs57ny3dnPOhs/bIbTzJOYgLEGbq+cPwOkBPtHyY+BbAoVmFzgGyjVo+htFC81LKvLtgOMRk/TcrS8wdrRgB4NvawjS5Mk2+0LNGitxFrWZym3KhKNa4oTM6d+UE0wrGWAsOcm/fTxxGRETf4OR+gHpjz/4ED95uAboFk9h317NMudz9etX0IjX25HrBDVRH/cBJ4Kw2G0iwIVXFWQOfyBGIvQ3OUWLVd6pq7cKnYEv9V98EfpVuJquGsoDb08lJZdK72qzsxlIAXMSqhYjvEsXZD5w8wiqUeMs5XuN/DnzgDl8OdPGAUnaf8f5T0NEPvQjNMN4z+aiw==\",\n \"d1c3d80bf13fd42b3e8582a15a21172d23b80247f3b6ca85ffa97f018db4dfcb\": \"nZ0lMxZNxcUhIGdtCyi9FXVtv9WF43rLL6S5nCcQD5ufOCkq1YNu0PqtK7c6g89TZlysL0dZ/xqWIeIvsn927cVF+ugd6jvoFFcpmr33scLhCbT2PqN6A1oSYA75CgBrzlJpDzZiV5uwZzBWnlu2/menX26DPBbV67Qz2Fbn71+XwW1aBn9DR1i6MilHdyVMLpZn0C1gpgE1TG41//OZWg2N2QdACzAYK3UMoauKVDnDr4x3+cgPj8cuvjgMV3xk\",\n \"e5273e3a62dee1a16328471a471f3a361eee0de07de68a11b6695c8483a4eb88\": \"+mHCqf0cQomJkl6nWynCwp6sCWdbMlZvd78BdV3SkFJcwwolpXPAf2kZZYeUXs8X6HL3xOzFvgJLiCyS2gQ+TrG4iZThREyKwgw5eZVWv6AvxzFU5UntjCP/8R4uVWjaRNf4fupVWANkqYFZmcL0mxGk+b2y0ZH8e6kECzHCT6ZQDXvQMtUtFcg6UfuWNNmf3ha+A/M65Hs/m0q4ccp4Ww==\",\n \"e755700c5bcea4905a1a3f900351d8a5564098bc081e6754e379f805728d190a\": \"+aMdZEEaKoLp6RGQeKcp6QbjVdiqEsRghVOLxndG1+abKpoajctleyvXwAk5IszYUYyQoLt6Aj62v4256vygQ/mITuOkN3OQ6l3EaYlUwi79fu5jZvhpzgTLIXWglogaCZEft8aQ12G8m8DO6+GhLI/KqLLGGOayeB4vIq9aMjcnWIEYq5o/vrtTpVGnJFKNgAP9FbiLQka3RvJiEHWw2bMgpKspS8ai+fLNR5NqHEfzQRlxYVcK6ieySks/AcQ4m9X0olJYa0Hh6Cj4Md7pHaYvkMioFshhs+z4HFwnP0Y=\"\n },\n \"url-list\": [],\n \"infohash_v2\": \"d655cc657b0b56975c2596ef1c493055565daccaf2ae29cafdea22bd7cc80e6a\",\n \"type\": \"Torrent\"\n}\n"},{"location":"fep/d8c8/#hybrid","title":"Hybrid","text":"Expand/collapse Hybrid example {\n \"announce\": \"udp://tracker.example.com:6969\",\n \"announce-list\": [\n [\n \"https://example.com/announce.php\"\n ]\n ],\n \"created by\": \"Example Torrent Creator\",\n \"creation date\": 1762216376,\n \"id\": \"https://example.com/torrents/torrent123\",\n \"info\": {\n \"file tree\": {\n \"tentacoli-01-Small Town Pleasures.mp3\": {\n \"\": {\n \"length\": 3177585,\n \"pieces root\": \"e755700c5bcea4905a1a3f900351d8a5564098bc081e6754e379f805728d190a\"\n }\n },\n \"tentacoli-02-She'll Never Come Back.mp3\": {\n \"\": {\n \"length\": 2778997,\n \"pieces root\": \"d1c3d80bf13fd42b3e8582a15a21172d23b80247f3b6ca85ffa97f018db4dfcb\"\n }\n },\n \"tentacoli-03-My Son's Friend Is A Champion Pisser.mp3\": {\n \"\": {\n \"length\": 2333412,\n \"pieces root\": \"3b458ec67b680d056b9f48d2920242bd812249cebd1939880340e7d2e136fe0f\"\n }\n },\n \"tentacoli-04-Summer And Winter.mp3\": {\n \"\": {\n \"length\": 2562622,\n \"pieces root\": \"3ff20c6f038c048f27e428499498e1d19488f8f934df6632d5d4d827ccf3f3f2\"\n }\n },\n \"tentacoli-05-San Diego, Yellow Cab.mp3\": {\n \"\": {\n \"length\": 2796486,\n \"pieces root\": \"7ad85e1400e68597e006040d4d9373d2a05d9b2ae13f14dd9939317d33ea2c8b\"\n }\n },\n \"tentacoli-06-Happiness Is Having Two Killer Whales As Friends.mp3\": {\n \"\": {\n \"length\": 3863849,\n \"pieces root\": \"d0063c2d1f1c5e3b5e163383d5f5a63ac983be59708e0d695870442cebbe37d9\"\n }\n },\n \"tentacoli-07-Too Risky A Day For A Regatta.mp3\": {\n \"\": {\n \"length\": 3696387,\n \"pieces root\": \"4767dc7f1b8d2f6aaae276034cb2e2c6c656af56683e148f0e54c15382273245\"\n }\n },\n \"tentacoli-08-Sorry, I Have To Go.mp3\": {\n \"\": {\n \"length\": 2583746,\n \"pieces root\": \"661b7dc0bb901d026370be0b73e87fb6a95479dc85bcdefe61711d8fc5b236f3\"\n }\n },\n \"tentacoli-09-Scotch For Two.mp3\": {\n \"\": {\n \"length\": 1924024,\n \"pieces root\": \"6bdfbd145a64cabfc9b0971f87ae69c93bd1847b0c48351ed971c1fa4e9db97f\"\n }\n },\n \"tentacoli-10-The Killer Whales' Games.mp3\": {\n \"\": {\n \"length\": 2463949,\n \"pieces root\": \"e5273e3a62dee1a16328471a471f3a361eee0de07de68a11b6695c8483a4eb88\"\n }\n },\n \"tentacoli-11-The Capture Of The Giant Octopus.mp3\": {\n \"\": {\n \"length\": 1783901,\n \"pieces root\": \"91b3f6f33d7c2cc4752c3b6e79bafe7ec2d3e03c31ae3790a5c132d1a9b93f84\"\n }\n },\n \"tentacoli-12-Two Old Kids.mp3\": {\n \"\": {\n \"length\": 2965602,\n \"pieces root\": \"0b09f31a4206b8c42530dc7fb489ea52f4db6992b1de8c9e56dd1bf711d64af4\"\n }\n },\n \"tentacoli-13-Tentacles.mp3\": {\n \"\": {\n \"length\": 4319196,\n \"pieces root\": \"26c6744b0b3245ed23691732c22f95a9bf074f32a49ba5c9feece809347e889e\"\n }\n },\n \"tentacoli-14-My Son's Friend Is A Champion Pisser (Versione 2).mp3\": {\n \"\": {\n \"length\": 1555839,\n \"pieces root\": \"736490ac9a57251d5feb02bb8e17d0f29e761993bd604ebece2f66e8cfc24a76\"\n }\n },\n \"tentacoli-15-Tentacles (Versione 2).mp3\": {\n \"\": {\n \"length\": 5261174,\n \"pieces root\": \"4c94d4d2d20631afa6fa4d4287af4cf5c262c00f834c027c9df3f711adedb2db\"\n }\n },\n \"tentacoli-16-San Diego, Yellow Cab (Versione 2).mp3\": {\n \"\": {\n \"length\": 2877921,\n \"pieces root\": \"b9f50e8baac6b38dc2f84e93387b8c5e21c334f54f8772cd5dc819e88c671c6b\"\n }\n },\n \"tentacoli-17-My Son's Friend Is A Champion Pisser (Versione 3).mp3\": {\n \"\": {\n \"length\": 1079679,\n \"pieces root\": \"a2781d81c2dc33dac179bce14390485e47f2129ba98ed4efbd4690daf7229d95\"\n }\n },\n \"tentacoli-18-Too Risky A Day For A Regatta (Versione 2).mp3\": {\n \"\": {\n \"length\": 874993,\n \"pieces root\": \"c67de66bc0c2c41fce411d351d82dff8afe809e5206df6b14d536d4ff4c00dbe\"\n }\n },\n \"tentacoli-19-Tentacles (Versione 3).mp3\": {\n \"\": {\n \"length\": 3951350,\n \"pieces root\": \"b6145263d70c69c630d59dfa07856663b33e5621a62a33d7cd706c3aafdfe2ed\"\n }\n },\n \"tentacoli-20-Sails.mp3\": {\n \"\": {\n \"length\": 4358356,\n \"pieces root\": \"72436d6a0686a9a83d782445224036c71dde61e1824ef5ea230462ad95daa11d\"\n }\n },\n \"tentacoli-21-Sails (Versione 2).mp3\": {\n \"\": {\n \"length\": 2870766,\n \"pieces root\": \"b27df2732173f94fb73fece8d7ca2864d2c027d5186f078d30c01e4310d700e0\"\n }\n }\n },\n \"files\": [\n {\n \"length\": 3177585,\n \"path\": [\n \"tentacoli-01-Small Town Pleasures.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 230287,\n \"path\": [\n \".pad\",\n \"230287\"\n ]\n },\n {\n \"length\": 2778997,\n \"path\": [\n \"tentacoli-02-She'll Never Come Back.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 104587,\n \"path\": [\n \".pad\",\n \"104587\"\n ]\n },\n {\n \"length\": 2333412,\n \"path\": [\n \"tentacoli-03-My Son's Friend Is A Champion Pisser.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 25884,\n \"path\": [\n \".pad\",\n \"25884\"\n ]\n },\n {\n \"length\": 2562622,\n \"path\": [\n \"tentacoli-04-Summer And Winter.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 58818,\n \"path\": [\n \".pad\",\n \"58818\"\n ]\n },\n {\n \"length\": 2796486,\n \"path\": [\n \"tentacoli-05-San Diego, Yellow Cab.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 87098,\n \"path\": [\n \".pad\",\n \"87098\"\n ]\n },\n {\n \"length\": 3863849,\n \"path\": [\n \"tentacoli-06-Happiness Is Having Two Killer Whales As Friends.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 68311,\n \"path\": [\n \".pad\",\n \"68311\"\n ]\n },\n {\n \"length\": 3696387,\n \"path\": [\n \"tentacoli-07-Too Risky A Day For A Regatta.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 235773,\n \"path\": [\n \".pad\",\n \"235773\"\n ]\n },\n {\n \"length\": 2583746,\n \"path\": [\n \"tentacoli-08-Sorry, I Have To Go.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 37694,\n \"path\": [\n \".pad\",\n \"37694\"\n ]\n },\n {\n \"length\": 1924024,\n \"path\": [\n \"tentacoli-09-Scotch For Two.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 173128,\n \"path\": [\n \".pad\",\n \"173128\"\n ]\n },\n {\n \"length\": 2463949,\n \"path\": [\n \"tentacoli-10-The Killer Whales' Games.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 157491,\n \"path\": [\n \".pad\",\n \"157491\"\n ]\n },\n {\n \"length\": 1783901,\n \"path\": [\n \"tentacoli-11-The Capture Of The Giant Octopus.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 51107,\n \"path\": [\n \".pad\",\n \"51107\"\n ]\n },\n {\n \"length\": 2965602,\n \"path\": [\n \"tentacoli-12-Two Old Kids.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 180126,\n \"path\": [\n \".pad\",\n \"180126\"\n ]\n },\n {\n \"length\": 4319196,\n \"path\": [\n \"tentacoli-13-Tentacles.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 137252,\n \"path\": [\n \".pad\",\n \"137252\"\n ]\n },\n {\n \"length\": 1555839,\n \"path\": [\n \"tentacoli-14-My Son's Friend Is A Champion Pisser (Versione 2).mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 17025,\n \"path\": [\n \".pad\",\n \"17025\"\n ]\n },\n {\n \"length\": 5261174,\n \"path\": [\n \"tentacoli-15-Tentacles (Versione 2).mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 243850,\n \"path\": [\n \".pad\",\n \"243850\"\n ]\n },\n {\n \"length\": 2877921,\n \"path\": [\n \"tentacoli-16-San Diego, Yellow Cab (Versione 2).mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 5663,\n \"path\": [\n \".pad\",\n \"5663\"\n ]\n },\n {\n \"length\": 1079679,\n \"path\": [\n \"tentacoli-17-My Son's Friend Is A Champion Pisser (Versione 3).mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 231041,\n \"path\": [\n \".pad\",\n \"231041\"\n ]\n },\n {\n \"length\": 874993,\n \"path\": [\n \"tentacoli-18-Too Risky A Day For A Regatta (Versione 2).mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 173583,\n \"path\": [\n \".pad\",\n \"173583\"\n ]\n },\n {\n \"length\": 3951350,\n \"path\": [\n \"tentacoli-19-Tentacles (Versione 3).mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 242954,\n \"path\": [\n \".pad\",\n \"242954\"\n ]\n },\n {\n \"length\": 4358356,\n \"path\": [\n \"tentacoli-20-Sails.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 98092,\n \"path\": [\n \".pad\",\n \"98092\"\n ]\n },\n {\n \"length\": 2870766,\n \"path\": [\n \"tentacoli-21-Sails (Versione 2).mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 12818,\n \"path\": [\n \".pad\",\n \"12818\"\n ]\n }\n ],\n \"meta version\": 2,\n \"name\": \"tentacles-tentacoli-1977-ost-soundtrack\",\n \"piece length\": 262144,\n \"pieces\": \"DplxVW7qZwpqi2b4dkeZeSfQxe00lI+NIAPgqutlOvUJEOiin3DotZcSM6OWGcgmsU7Zi4oQfSLhO4qXdxP3td3x5UL8eHeIpnaJE3Cme9gqaBRdEIfFCVgZanXKMRnzPQ/aoscXWPuiMX0t1Ytjqvag+4yiwxKeeK383m6l/sDQxKceDIf2I/+BiHa/q8LHVvYuAMSGLTXmHzU+bUC5V6cEFfcp/Sd53DeP5gwSPJvNyXX+parYLrqrh4BzHhHRwBluiHyecCUuLcEYmY17+5oYtzyF9og3UtK4MgXUt9MftFAColm6XgHiGTHgy5gn9p3MNL4k2fns1tUAIgRelAMnVF17WwtHj7DQqW3uq7g+9V41bsRYvyNaACsDqxHgB/SXtAqiKGPowh6UdwID9H/BmMrK3kPSuev/gniVE++W7XtZVWecdptUWZQlRMWjaQIXEEUTJlweZ1o4DBVMnyQZU2ZFkTySWCZkpjyUq7IBZ0tLnX4Q1bWW8JOH9wdw9I3TlBRIn05YTO7DGN2+d6xzjjbwBpl2TVkLo/EvyAckO52woS6CLeP7DycsqFmpJySSpbJ4m19RgeC7VZQFFed54Hv/z7mQURMyoPoHEM2anO7UZRun+aHKw2+1ugOKWX5JEDwwEwbFTDhiToHaKnlC0OUJhxTBB9n8BtICTtdDHHLOdg9Nh/E9qZBA7jZHAu9XULXkTyuVABWvA71ieEagDlzRnHjfFmRrnFg6MsQGKVc9QTmw1OZ6eWSqosJVzuIIPsBgwgkPrPXkS0CT3l/DwtkWBRg8TsGz3qOKH5vBWlWiDuNkx5QpfNmpECJRm6XX0n83NXXhXFbAudU7iLvj83LeKRBFNoUJG1asXHGFmd65y6GiX2oAgbOk/3YMHfb60fVEWV3QGOIFwgfcfDFBP8luZiuw1ld95G2W18TrJOSWaaYZnt115wT5x1h3xIi6ide6PCn+AXNqVTRAR0TmQhflW8/YP7EN0RXXTVnBPIUtIWH9xNarDAUHoAKNVZOkAHJSwLv691FyFmD+gS8OBO0OaH7lgC7uFlpSC2P64YYbixnjq2QinqLaPKtkDv4cgCRJQ4ns98qEz9Bewg7f/KbDd8nXJ7Bws0ZSLtPFiwq1+jvlMsEIIOCGldL9TCX39TsRI3uPY9SjW55XA/FVVVkQpvZMn2so+kqhq49UTJCmmxNpCh6lUBSu11x94HyYzTVu9KTaO9s2RGC6RtNpO0hMSjmEOrRQyvA9IUFHAQqqnzjtTQ1BgHOakcUb1k2ziyadfV4DJr1uecQ8GXTTCcQPeQzkUc+HHeezsQs5rwDkQ94j6zqfjgI2HGTlGVdh2QOMJOaRgTzFVIlWZlO/jRo5ACovJunOzES+C2mqhE68DjW81Z91ywbDha/4yE94W1LOlrsQnz6dlE5Ed1g0vedkogdwdyWfRBgLOcAgoQj8iWM6YvDr4orlp/zPtYEt+QLNJY6bGLj1rPspHKs3pKOeyEi7vpNDpY7M+3Mgaqb+NqWj86n9CJGAnXkEOXuG58v5PgJsyBmeQcjLaeYMVhQ7HARFu4ZHy9hi9QnMMztWiiW0M4dSlTJsi5Kcrrfl/yDLIoJ8ZNwcuCJbnKEUT50UDYHcFLscR7ZjJmGBGblGGxs86Q4U4HQguOZ8eBllBCb1gjCDegfFQq+qaMQhHH0tsrbVzpJywCnProjvOJyAyISgMKuquMIUmLUIuHih58qhZqmgsXxdJE2t/eU/asAZmVTBF6fGZhQEAfGg3bdqKhakY0O4uZZz9b405sRaaUaCK88waYQrS038AIxgroCNYzdtR4scDBwqTvVffEqZvJ2hIuBI34DtKkvuwLtWL/S27tlHBMOwmEbCLWMQfrUUhroC0ONNlcElA5g54W7edDZafI6CW34ImyEOIsljk27IRrB+CKIuEqCgLh+HxAnpUp2nNPRWTQHEMvkfO46i6dMBvHPH+Pilt04UfuTjl9PSHO7Ry6XDkssB6ANASH6UFW2IEDiHnRfL3VTN59DTtg4e5uUBxlnIw1b6T8XUDzRjGZ1tYLSCbA+7U/UT9z/Ae/YVuX5YgoTKTP3WwMmQ6LtFLK1PnXM1ICuzJXLijBmkqu28JN9l+HDxXQu+oZOGH403RN0U0Bo9YQWXpgljLFj9jifz6rFOGZLL/gdFHomVRUY1+w1/BcB+MLFKxOzrovhBxV3jzL1dRNiYSQHFFRkt2NtCWlGhga5s0y+xkBM37GBJ8ykYEieAV3LdUlWMIlaGfLvO+938thoV7MK2JC0dNhvpL4GwIBymhGsuUaETQC0vjB1BT4aTo8SRqHK6B86M8kmNNmk8+eqvE1KNYYLofAkf/FGtPwaRQ9dAPxF25qrF1JUw88H8HDZjgmxxYXlU15gn3CM19TNY3c6elH50nuPvBHKiXL8XRVGPRbVYS3lQhJwUOOYqf6kbXasuLpshTHx1IVuZWCzESNHp5To8JvY1WymiYBr2Wx9dh9lpV1qOx25p6Zm/t5oM0VJ/xFK/Q5+bWQzL0rE4Z23WsFwFXsJj3tHE44CRkVfWN3saz/QnkA90QVWwn/ACyK2zm9yaEGR/FXr/LYj3Dzx1U+oYK2I+s1CZfdO3D7VaH6pV5hqQPmW+LSQHUx4Lq0W+FGIT43kumRaxrtuj9fS9n428czT8eb5ok4UZS03GiYCSx9+TL8Hu3tyhhPBKrGmXbLBDP5ZwtGUBir1s9hav0j2OgiIggPMBWKrJSp1nfZFpfN4zG5YL4FAF+ZcJSaqmhoFFJykiQypaMh6eGRoTM0l6Q3ca+SR9cK/QwvcoARlP/gEvRp0wFeb8THOUWyEhYL+WuaMC9Glj5vW//HekRvn6HzDoYepcTFZRnPPupwHPF8VLa9TxUim3PcqZbufhfh4mGxzAVgLCDLxLshINscSMDI0DVkzOJFGEo/0r/8GnuE61CH5FMdaIwbJWC0IamDbCGnoPwSa2AOOKsgU3VkbhgariWoyM5+3Cg5+QJprRL8fhaqiiLg5E1oK4sMgL8C0xbZMOiAj8fIihjxQjwIWx5c+kjgB9rdp4hTUc9GV8uWCyp8fDUNsrIKP34uFqjpwabImsK93OQT9jMYiCQ7QmzCCF/MBoQbH86pEPkeBbtij3u6VOIRgstmz7kdxgKQ1LDSZSvSRibyguLJJaJzZWGYQgwETr95R50Qeb01xLUzar6MV+gI6m3Wv2s9h9nqzvREvtSD4m0pVwGq0ej+gxR/lJquvLfwaidL21U1S115gXko5GXknnMHF9reNTxrmJMRabsugb4U3Q0TRNhofh91BwbOr0nVma2FI5jA8eNSFsQGJYtEoj31qtawjuLwnNDYQ1fDDKJqm5ZiqBQRQTrqUU57/9FsOn6yvSNoNNFI481M3TOIXYfbFUM8wEYR53Wi6A/tRqpidyXvu8TUHsWlwPWAKsOO5qkb/oIQ/NfWeefit2nZUvaSyPcW7jqtjs5IEo7q96hIbseJn60oSNZKtBizhr6aaJKDFZwDxTlbbLg634T1Kof7GjcphPc2ODUaCm4W/M08+9t/USPugMHpiArKKyG5dBBqZkYZK03bmBpUiFO9aJKfD7K0SjcCcSdBQ2T3mDxGgUpUDc5IOQdcxVWvWH8qNIOnl72abNzO0b0kPC+E0CsWmBHhYlV/oiAl6ZwtvrUtGH4aGC2V6M+ycLdHpat0iFxvijC14dXNPZcPWPumyk2zTjUwaT0suhOeU9onB15xrIkUDOqChuNNvMQPXO/cQrKG2+wdSpjZboWuDtj+ElbxXiX/DMMyCLLmXxz2nnWfB3AjwMi1pdb435WG3rV7a5mE2i4cmHNcw+YkXBHyc0pqBxH0FiOtGYWtTAJHpNyvpStC8mIhLq8Gy7rHoijvCdYMcgtRlchc275A/R23wB2HaYbNDV6lj4pa7YWU+vWfP1uTjh8I+csOA4K40IiNLK7/ZwqoWoi0wRGSxIwICx5/t6WmoBO/u6J8jFKiL0pMgVBkZFPY/XANqBS9eku8NfhTSyZ/eiYbKH4WKGkg23UY+Q0H2RC9lAcXQSFv7WjViEqa0H5KbNk6xC3A+IvJCi+0BycWP3gs7qY9hVF7ONA8XP8pTbXI4me/6nZ0GZyam68AXAsheMJOTR80g2jxmsj+PkLZRUFvDlceL9ehSzT0wezNufHgLYHgnMG8H4qvCP4mtWxGUbuNeHSCIZT1gBeOLa3QKT+whRwif803udJdc9OHRAOmCxEl9L0yCOLnwFI3onJ06aFH7A3R7TQTKzPyY7U7tcxDL4nNOs4X5XydPAzwqT9OKjKBfgCk4AdfeaES2dLWU9ztCDFVhShkI8Y9XVI6zqFHEy9laimO5e6h4cNbRuyAqt+Qjo67JDKA96T84OWvwHQCErrW/+Ox3slhe54THp1Rts6fqT1d36+3a/pV+9IQzTK00osQRLHhR/b1XaGIWj37Sfh6LPXb7hVT4hdW0i3yfbc9LZSOhuB06olR/m9e6PKUVJixEuAXbfSSYHlJpu6+WxfDBWkOoAqzU4xZV8TLXocq5BZOOI/6adHTc3Jwfr1fYtrXKz6esT37x4yCy0iD/rklzZN5YzTMA8jmnTv5/wLtKT3Hl4g1ljw/bEv3eZOM+GslMVW59DuLu3n+ez21tDAbl3EY9z5XHiP49D9fQ4PuA9WOZktH9FA3IqS1+QqBH/reUL6ldye6IEYy5wSFt/++zO3MuW+EO5Fh1Gf+Awoi4JlFApscQQDSdC3AUvwg5gqBprnmIRpjlNv7LTJzonTPGr3zploip6YYZD/vdSlKot18wp8yNNP6CRoYBfTtVdYlFO6SnIL9omRB1UK9SNRPM8y3XJWClt+Vcgm0S+9Dg3yC9HHgeY1YxWDG/xZmf7aB1BIEhypEmHgY3VQH+4pm+zIAxTvXBJoR5AJRsAap2XFNux2AxoFJZZ/4yqTRYax1hHV/O92gcpuEA+YmGm2HbAqGDNolznqYNXO1Hty7RdPnAYmMD9gplSTgdbVpDHat9fJVWhHAhjJ12lIbTmDP70eR5hVYS2cU2TW31VZ6yG6Fu20zeAzi/z/4Ls8XRJ6MZ4mTJ8l6rH8+53cksfrbO4mLb9UAmm4evKJ/ZTfOHnQ58OkFGy06OozC5VzWRGbuA4sdJfvs/27W8gd0eYsRFXfbklB3dgn+Bc0+S2j39ldO+9q2bLSwH+ovkbePj7Nb1hR3njzpVyLoYm/foFBmTDaKMb8Aw/eksBXd6ews0RSdZ4ZMIuyOru58/RxNLeOVdX7dYSwpCSRK+H3doP03wHkHSVNPbsxnGZTpaFoJYK4VAvaxqjFEqgZc6laZpFoer3HkBWagkXg0k6rgLUOIoSxwSHJ9E6zdq2e84Vz+yDYngB3n8rTO5lnc6SBPhlS9Z+6JHZeHfjTlppdY3WFCF8RWWw7XyVyrfu+idboEVTrlP1EDc2BYDvk1wu3JQW+XPSvcZ8MSqmsAx9y+nyHigXoMtC9+Vlr9C0Y18qnGZUixeadnsVj8H+Vu/UHd9JzdG7MI3LDDyMgeaKfo1ATzFApCXVAsrGf+LzYBGyWQ66aOx2ruYu6EnRdm/aJ70nYVmmwqpbRJxWWGUmCI+IuxziA2OcHzGnBf4cAvCrqUHUe62UMCpO1Mt9IsmYqgWj0TP9frsAMnNSM2X6CH6UZu+Z5lLpvZimV64zQSCloIgkEsZ93HM5VsGLwXormxrhiEqY6gQ01Tzkn8A65zPfeT1ayO6oIfPLw2w1GqPi2RLdzYg4Xai+GkzuuoDk2sr/mSP/aUaiPNBYbS+tAp2a0gEFCItChaZTc9kYvX/tpLW85KEsqSOZC6XeS1WaNuVOP44YKU1c5aJwSegpxuxOLBPC5kctKqheCopZIM5alNccONgwTS/dttxB79M3QhPcnysSV+7LjMJTSVoQmYy+mEXxkck+bFM3K8Ckti6ROgaWi9goqDeBRMFqU7nFxzpzoj+I4bH48r0SZ7/c5VnEifQ1SuOFePuq4dYoOqScca01Y3JsEpc/OYmZ+WZVqnLYmARI8XxHDeJVhHcOzAItLcoD4dUc0+om3ObiHCp+qqtkq5NH+ErzHG8i78XA8k2daVJqtuDBvnxqFXjxLB0/t9cASFe28OiBoKIIXayXbxGBTYdk+Hjt040SgYSebmVgxImaQMfRqVmM05IjvUGJJuAxPYfoYDPfkS/xegZwgsVzHCXWdcO69nLWO/hXjQjSOOtRYYqixJsWCWmioMsDS8uGgP1NTWb8+eSJJNd69EU2NzeJt9qojUbuT02X2yc5ymzCwC8WyqniXJ1sawgxASLPMpi7nYmRspbf2pJ6g+epVJImm8mk/CEPnSRuCFWNMQ==\"\n },\n \"piece layers\": {\n \"0b09f31a4206b8c42530dc7fb489ea52f4db6992b1de8c9e56dd1bf711d64af4\": \"OrmkL+1wt5DMFYBPxOPA5cH8hsrAtlIhJviOqd41/UovO12SSlbrFchvhvi5DVrS8CkjguZZ3kfha8mRcGGY8RAc/RPjlPTggnO/aWvkFfCtGWsLa8ABT3MQETa+aH2iYfah6XNpDHw5heTQBas94o4noUMRWj2vf4XeSUUYmCdIld3ZUv5EC/lkauDqKi+iYE86H58RLKSoqbGkns+IkwDJQfh9GBY2FRLtitEbukJqsBxwnHY4QWWoGlkas0FrcooYDBSak+2fcYFoipVLbi7fPZHKR+sJLxFjE8Sc7/hVXfb8usqNYn3EPPHB8jlMsL/sC8ZD5YUIj6iXgFWVR0hIvwMLLJ9f7fGRSmO/R6Z15aF+0YLzNyS9DhAxrxJv6VVbT5VKC6l1/QOSSKr2UvW9C78Sidm7/f+4K1yY0XiPWlQyGODUMrvbBlGZTPb0SVi/828r+YrofHkSv5Tz868yv7R2QdvJ7ldV+RrM5604W0MAN1Tip6BNQndW1CWt\",\n \"26c6744b0b3245ed23691732c22f95a9bf074f32a49ba5c9feece809347e889e\": \"93cms33PbZZTwmgqv5mck+k2grLln6vNXYm1D0H9sr626dMzmAdfQif4o5BAAMScWvQhvRYscGWk1tjplEPvLU5U3OMyqu7tVPITvQ1w1lw7kcozYN4t8ePhlcR/nrE75oHF9Qm9xJAMts3GWvYxUROFkFilCLZd82NQ7Q0c6RKBNy5+y67rQ2VD/+AJFUuSBKUZSD70v12TdSRCL6T2xXsf/3G5jRxwaOVPsfnwinyHHz6FShzXytspL6XSAvXOoxH54BB8RmUgqyE+f5ocs1I+jpnx62UGPhIeU9por+UdwIjtjCFBbAYCXnMXBPfsVPR0HcsDgzcKwoWzvap7gJOJWhRRcB15OZiHQL9KJ6GRE1/ugcoEq2EJbw1Nje5Pzm8TXIEsNZyPVa5cN92E19hFFaOM1ZDKgq4lZgMLu1hrfvaeisk2TyI2TF8znoVw9CJCA0XXYI2e2XIH/3zXyqAYBn2h1un8MFpgBDkU+JblBiAp2L8smM7BBb6IQVKQpd86WXtrpWv5rSld2R94H7AUhc8xkaTZAa2vD8sEackjZBC9S02hWDEpGNl0ohQTVikghfqiW+zrOn7n7CIfcTHOp4SVJq7Qpi9ttuyILC9e+QI+MV888XrNcB29b7Gbd3M2/29mflYtJ7wBneZZdtlQGxgFXD+Z/pNktW5znVu7MzTvUhiitUbq42r0+4aXIa7TprFx3vsnO2IHMvKi7A==\",\n \"3b458ec67b680d056b9f48d2920242bd812249cebd1939880340e7d2e136fe0f\": \"nTFIf0LPwCPoNT78+kgBFHGy5XDvp5NA2p61K/z22PXj9kdCi7wPdvqK8jL2fVqwde66RhXHGbT/Wlutw0isomSetuJEWZ9A3F/TKw1rSjKPGz9hOGa+UfxjjUMX84QfVscP7GVGfcegWQTM87L2o/Q9VKMaQyNPUldSCebK5fSoggX+7LT+5d1U+Z1gaEI/GiCm/D0tfLh6ddXQhybKgZTzoP+zvDor7Mqu+4pGKE1eJHU5h0Iw6DdgGup6vfoOcTFY0flI4DUHi5JMR67XXadcpa5sslf8nZ0AyQC+PEe9+jI5yB3Y2Lj5V3Ah9xW3NJXruPOIZjeqCDXnZ+/Il6VuBvzhB/16jX+N1PFWNDfTP5dPGBaZJz15IYLzPUCC\",\n \"3ff20c6f038c048f27e428499498e1d19488f8f934df6632d5d4d827ccf3f3f2\": \"pUy003/UkvEJDvQzN5jXhX2B5truw0pqVsMHdPYMdqepwWcqZEBrtQvOptgzTBmxBmpyVUPoGed4G1ls5CuQOl4xV3fww7zU6tA/jUAZ2ufWbAfeIayiO+LdgX290qihHmni5a1MiGgL7vHrINB+BBnYJ75BSOFJ+Kcb0J1hBgPCleZZSHUe3y4LBo8Ten/ocrGbCwPAKzOr+g3sb3gt8QI9jKvB3e0VKGBTF58QrjoihLIdPiV/F1W55CkQXidVijh9FocJLESmWdf2CFm4mNOxRWSrOXAEOLuNwgs80pg7uL0qnAqr55gJbZLyktaWpP+cVNh5DaW1I6WwjCsEMWLJKvyGyWN74ibh3taHnGD5FFcw8ytSiFBcTeWDZl0pAuZZaxZgq11l8GuiFJjaMHzWhTixWoqOI3lZ8TUx9Yg=\",\n \"4767dc7f1b8d2f6aaae276034cb2e2c6c656af56683e148f0e54c15382273245\": \"X5RLsarlc9pADwVgp2d7PP3hZN3ZmYlep0pA2FuskDlbKIx3Rm0aNQJO3I0WeALY06cjog7jS9JXMMPGOZjSwfTPnvqPlOrpBmaR0/0k4UsHeV7Ma4D6Bs8TRdGgGIpmL4mhG19GMmNATTaCSTleOdkxLWg8/ikCEbk87hrqjMDTK570+4Z/37L075ZbJTZ9PiBv0d9otlcPwFwcAG0n99SCx7awBNSolAofK4aduCt+p8TffXnfeG72Ng2hZVCDIIKliAoguzDaGly0iyRkD9yxD7LcM6FTtcCpXYEfQBiBcVkQkZ5TclFCE7XPNpMdtu1ft1BPuisUcqDp7OYdGtvnHkjTPwuuYBp6XIbHYeIDIWfatuSmlzP/abATYrkxDSIHJTBSdOnTkY/9kK60QZ6wtbd10RpAdMcFs1QeMaP4fAvN1GrHwfaT+FBuu31S7MICgv6JzSkpRak9DhD9xoyG6H2fTwWPBY4s+1eLYYCwcwf7n6ChO4VHxZI9Yqk+aGjO/PYTlohOGDg3VeHxgGvVEmoDt+VA1nFtqvi4UPg0P7C914o4FzQrZjpqelSMKFCRlW5uWZnNH1eIe0XA7kSr6POILRwTYofx5Pl23MuDjsg+YTWcTukUj1pKqwuo\",\n \"4c94d4d2d20631afa6fa4d4287af4cf5c262c00f834c027c9df3f711adedb2db\": \"DyuboUHYom86yA33liC6obKdfKiCuOiyTlNTt5ACMtZF6TRLlaolBIUHNuLUnUPbfTTyz1cKtR/jFWwl563V8JhmXwSx9EvlfbCwIFulQBWcy9/35DHrRoIcwsUvB2Duu2Kfcd4ICOKqVvzOMsIR2+A1C6Ar+UCCADt5hna2ewVAFlQ/TM5rrxNioKAtWgmowt2dVrmng1dOX5cq6b9R1gQOB2byxyBbQf6oAK1royKWOyLMgVjjnGxLbpkgAAQhyhp+ja7MzGYF+mYo/ohSpjok0tfm0cEbG0k4s0Wgqlr3TMLdjyLTHzVGsPywKTwz/kUtrB3JKMWoxIqBjqWjTf+judP0abSv0/R6C8DhLk6x6oKuFeClww1cjoHfaP+OnSMHjZDPTw7gy+9oKVT5J5ByCWrXkUi760FhEQRXeEG2QAZkJTSgnNfVEEU6n7oU2Toslqh3SpMGjgjbZDveCxw6VhEP6mhZe6QyZ0fSHbaTnlIEt53jrUk4UNzEljVqqb08/csCA06T6mmBaAy82VBj8gRLSzfsWc6mQM2K6cG/UGd4NxdFPJgHMReLgU/xYiAiWhurTbjNhW0wwS86+6fbCPehWnvQq5rLDjtt44yohRMlhOIuPNgQeoQ5YUrtB/cQHki6Wg8bc4OyKMt8mrRBqR0ef5SS8aVt8Lt2TzT+AEp1IYwau6BEO59JkwqhF5F1uZO2+jBB449Az/B+O6JG2vjOhfnAw7aJgkJrUZICgzPkIzMfXEGTGmDzBI5acKeGeVt2Husv7794wjbdw5Lnz5k2vTogv34wNhYoSsUiYpFkHw5iiYERu+XfznAs8Zu2iD1oVRv4MWF8fnYJP8nwjOhSwrjBSx9HRaTicrF+sr9wRZfehlk14qN9E1Oq\",\n \"661b7dc0bb901d026370be0b73e87fb6a95479dc85bcdefe61711d8fc5b236f3\": \"gATN5ZHot0T6R5YneYJPrP76oSoC9fI49GtMgK2kJq0ya+06/wZZ5+RgBQoqFOv+5uio41M0wZoPyz6NsYrKFu/91+XGXzDoJQMsTj0PJFL/uXRyzfCtfNk7tfAGrEvN1Jstv3MVyPogT9zowv4U3zDpcPY8zjpfu46LkHI1XZyu6Uh2t/vrc55UO0QgSmAwr9z+mcX/up0n0ZaaTGNOESyHATjBon7HMt2ejPQ1MCBEtjLRPbhcdhEyJRW+A85aqFeqUNOLqTspBvqiio5peWQyQd0lIw/M3+Fi4WNJax+fR+DB+VPjKlaWscyje05QtNu42RT2iqB4L11jn0pdvw/Xfk3ooI+RBJ00u1AeSSQqZiY65sP3G6pTXbxDb6EnTgj48g6avUnyKMJCqHEY1/RhvDakG0n41l1NAT7vh7Y=\",\n \"6bdfbd145a64cabfc9b0971f87ae69c93bd1847b0c48351ed971c1fa4e9db97f\": \"SVct+bzPEHObJKHC3ZuofpIzlBylCSOFU8qsaGF2y7RrKZrjhlbOZdu8sE4rXUDn1csYAKCKwJ3cV1y3Ylg/el3ZO7d8syTwukJfpMfJE2yybMbQBlT/+AY4KncrT/IOD+3C6t88jOjaJW/VLiKY/RzeGmfyw3vZzVvFArsSLCxAbcrNIeATbLp0MJ57YOpgxiFgPU9+JUdMcN5PZtG86aTC8t+1Ky7MOcb2HzNdteqfVG58Hkl51l/xPFCHYZRTl3SkoOVy1+B8nEj0W8IS+1E8s9RemS4qyN2p2NZE9JBQKGiOTo8zTcyuhZPB9yxhB2xnfyCQenl8FwpfkQLCAA==\",\n \"72436d6a0686a9a83d782445224036c71dde61e1824ef5ea230462ad95daa11d\": \"slBceVoM9U+QRS9g3QIT+z/C08OGn3aAuwBuSSKGAzfCsDfRGCkY9IhFtQUG9wSjyrUU0Mh5oW6awq6aICOe/KQJCQ+7mM59RUBCkMbuME9Cr0ut2LlqAs1se4NtKgcMFwGnTtmy78Qu/q08tIAFQB60Y/buuHOaCtsxpWhoB3phA3eQRykDaWLmphUlXoYELeJP2T/C/5ZXENVQz79F8uyXTfTZj98iLqOT+8SxOKA7PsZsnIDRLWGWfgOic/nAZh1wJMIBRw90qFUmoYdQKaWRwB1hcQTObEtZAF+n8qxkRfHCbUnoNJTswB87QtG/hxaYclaVRZaCwgoLvoqYx85e+XGZEI83+HHxwto4xN/Bzj/JVtk2HFbx/QLCdV5rOtUKlJhsYIuOK2juRKCaOLSq5NghVf5alfy9qIjWWseQDHdPDiQB1mck2lrpZDJ+Das9f3b+ce4pLwxtQRW1JLOBjgBi7HXelPyu5lMRlqZcwRhNFZ4DxrprmPYmw0KeScndn5LHJXsDC1JA238+wIWP29ZF4QwLWvMPkn4Wfe4QYmBpeNINhPJjCwkhQx90Y20VBaJGKYK8/Le1J3VduaJ3ciYxDYcd7WW0ngaZiVKZ0y7pjfqC+tLT5zq+GQSGNPNXogL+OEaFKYVd7Lz0PfG88uaIkYVq0H7CDAjZwJZSnlCaT85L0Kv44jouiIKmfO9QX8zDS+QxLAh/Xy7VAw==\",\n \"736490ac9a57251d5feb02bb8e17d0f29e761993bd604ebece2f66e8cfc24a76\": \"KyDTfIYZYSJe7qMcrgvE5sUiV+0eus85j2/q76tIC8603rr7bx1er5UDI/7srDKX0/ah0ZdGtzleHKkkAuVwCE73C6iDxwEI5OESBF10mHRzD2ShejpsUNGx2wGKSNBl3m4XFs6Ta84bpxA9FvbbeBPsMvH8k1+fjxee4LqYxghlBHAOyMk0zMmvVGHN59cG7jnDon0o172flEf+UYrOnom8ZDi431XQ7wu5KVpfR8YqCjrAfLfPMhymEv2K17Kf\",\n \"7ad85e1400e68597e006040d4d9373d2a05d9b2ae13f14dd9939317d33ea2c8b\": \"/5ZiHJz/da3YhNv57jfG+6W5p++ZnjCPsy2QOJlC8m1iN2bmH3ygJtADgZZjbBq9lMg6qbCwEVfHzVNSgh6E62zNiPrHkguznFha+8orialK0xQe9TVnoMAS1fdMtFPkWFa8zKVXlLrmEiXa6MMDt7LmTGDOw4AFmIgTZ9+cQyJh+UvZYdYyO5xl9yVhDIK/HNSkhIe/E/gkMTBm/q+mXfER+uPLq1xc4eqykA+FVvJnBd4rzuDABEFUJP+sVqxqFFY28Ue+SSrcmHjnf8IL6bXem9lUcLoS176lXBOF73OIY9s1E+2vOM02HSXYiPXbsi+QE+cKrvs/PQ1gas5/EqGXDrW2ioYqebIX+Dw5re8xLvP67w3xrQh6N/UwyUb05FoKrgLFxisYn/NELkWiNV66PFccfCh4K0RYZUW8SMsHcwAEfQRst5ICX6H9HI6HUqkk++FQFWw1xmdPbcBKCw==\",\n \"91b3f6f33d7c2cc4752c3b6e79bafe7ec2d3e03c31ae3790a5c132d1a9b93f84\": \"n2cabuVybK3NWw3P3/XguQcIwT/JKcvTwjfIWqfqHQ8Sd1QeMY4bVjOol1ubEWU7BfhXaI9TS80wWoj7MKXCUdn+XkJXNh09vj1BoksVyiPjPT4powprR0MHolp1jxh+fN5uppVJMEtHYEMy1EcpU9zVAALQ5fwXL2XJ00+VhFsnoL+5NybXXGXB08Ihiy41rTkL/eDf9wS/xnDAooYsO+cC+ZZZJ+v7Pom/3pRU9soMFNJOKct1bsb7uuT9prOnKF4ppNEFZE/GwMezOYK+71WkMqjJHexFaEWiYLGM8mU=\",\n \"a2781d81c2dc33dac179bce14390485e47f2129ba98ed4efbd4690daf7229d95\": \"cy1UGgsHtzk1YlCUOzYxU5TTrIeXX7A6KiSlLj0ng+HzCKmJRaDGi0oqbkHJJTxA2UPrluSUJ4+CAhOYw7XZwHPVW8jh2ywDtSABmQvXEulGVJ/knxQCMCbnZEs5Sp+2J/Wk0hp6kJp0lu4Zsqts/pk1n/GiWxfN/1XZA1URfMu0GO4oWPDIPgpqFTKtUR0KL+AICQIo/7iKceDKl4EQLw==\",\n \"b27df2732173f94fb73fece8d7ca2864d2c027d5186f078d30c01e4310d700e0\": \"AwIURStzYR9k0OBxqnDmX1TL+6LU6aS9QO3blOLQuVh8MU/2F1KcBDskSVtkrJwfROiHpe9E8WCDMD0qbFRb4AMFBZhF7HeToUj175XtVeLU5WVWK8vB/gumLvQDTZqWb9tBwvcs8dGwzdVJfTNiIyIUl8eAVXtDEoICFgGyt1N85uGsl1BXTsY75p8pcKwAm/3wBVqZHPWUxYyjry1AKeCMCi09CIAn3HSD+65ewi9vwgNuhx3MZJQ6+3YZcHLWonWu2aGTHCPRHy7ERJ3EMgk3l7BDLoXLpTqe+/vRYXm7q7VwHyc0xEo6VeHLtWyeeUGUt23zWfxt9l5hHFCJ2ZvQVbJxYP49QCkvUbOmeYv7HAuWzl5f217lD+tnhOJ6AxBhMVZjWoy+IMg1y2C7mJ25nEd2J2yFfHf+WSy1h4d9IcPbYm64MSWR5TCnHKb1MSt4xdCjjTi2HP/8Zxhb9A==\",\n \"b6145263d70c69c630d59dfa07856663b33e5621a62a33d7cd706c3aafdfe2ed\": \"wfe1c3oZL51D35knRIf54tu+pUgpVC+Pitr0bOoSKXSKeKRvqfGE2aB8VbQLoEB5DjmEYteWfKtJBqN3i01k8aZiKfFLM0jpad/3LAH0ULeyMLRGPT0QTfuCzh46QjiSMa93Fnhf61OzQxqmt1A23pxwXeqwivAsehtrCthNeWncGiRl/8O/pMmyu6h6SF7LtfE7w+MaxB8m1MfQBvauhoTDOdRFAAlKm4wFkAHxZZlAixiUvKOchdvG31viM+OCyibWkTmkSlXVFWCp4ju1ndjS5jjzlPAz4lRd/D8YYoGEbUlK0WF7GNR8B+grTRE7m3CTLQBkLvtxsfPfR+TLEL/iPc7AQ4ZNZ+4ixzkWPISuvu7vZb0xByx5fgoWCPJDiRMprGx+GG8FjcMAI8Q5zpOCBVr5S+tQZpF6gUQJPtLXcHaWfXGy+AWGPWzEQQB4QzutKPTkZT/GteT9I7To+tKSsTv7bnWcfZrtBbOhpBUHBAl02W9vcPCwwgRpDLiWpGJfoziX4bmmqq3PlAdSf6D7pvOd5NJ5gsXFayZJHrwgZ8fO1zO+CvBlVEsTSEfOYxn6k4h6igW5Cjo+hPXRlF6tUi+opqd3oJK+AvER8exB4V3K4pa0eUO7ICA9PViKrX4eeKwRSurxzthIxR1SZyRIu+u80l3Qp0Vjn7Rs9ZY=\",\n \"b9f50e8baac6b38dc2f84e93387b8c5e21c334f54f8772cd5dc819e88c671c6b\": \"mlA7x/hs+LOUdTamHzEJZQZelGfXHa0EIXd8VZDLe1tnCTQgWmwYXfhSeJ6Sfsvz2J0t6jcLHuYM1JenXbNCnWA1sblB+D4aHQs/GWbP15cOKSwp3Vzr4m4VdrbPMRXdHFVn8cE/QRa8akYhme/uel68R1RyhgqvDZOsD1Ml7EigGtsKa0wzu9+kodzCCtCI+qruIUa64BxQDLTcW21fGpLeQZeaDJAIr/DrzSs9s/z0OjI1XP7Eo8VGGLFn7zO8dMT69LLyD5XPRbYIz50+/AV/2+Bn0DaBYy+f6AZJVAiCVEj7++2CuDIyAzNltb4SZSRH6fjyrmqDIo7v9PfIy7iXvTHwuJweyfEuDF/l7iKZsWjMlLT77vseIiaDqCBsXuMPg4cZCF35MXmfmDObco0zvUBxszujKyrGdKxx58bRnTgPPFh4h3lsDwKzJu23F60vsG4V0WNQ9tY2tiePfA==\",\n \"c67de66bc0c2c41fce411d351d82dff8afe809e5206df6b14d536d4ff4c00dbe\": \"+yKBmhJqkSsy3at/jXFIFvjZbUPT6ayJHsBtWeIP3mzHHflO6MUtioZiVQ2VnnA/PnR3JelkcwKwMRXmfm7i1zhBM2Awl0WlrqImvM2TBvmfMVPdpyWwejTwa/+qI79szw9waLwWYOdmu+jAwXk2hpE5naPvsm2lRuz1x1ehpyA=\",\n \"d0063c2d1f1c5e3b5e163383d5f5a63ac983be59708e0d695870442cebbe37d9\": \"Ngl7rHoMejoI0eGHLHI9mb0ELNrRc7h4EZCiaaiO1TQSgBX//vPdlS6pptsB62ocxGacCH+GrIhH75QNhwxAEWwBWFGDFofCVii4EctiGgBt3CpQgF7Jx0ce5AGxvD2/PKuV5MribIC893ftswL+NQFsT8Vp3N4O+nvWr2Q1/5avfGwkblyHAhWXIV5w3R9uYht94HxH0EjYZrDKCzkVr2ZypdlMVeStnJPBuJG5g+YcZMJBGFJvMxzYs95AeluXmh/dIIEh1WzPzVhaseC6q2z+SiIV5ZJtrH8deVNI4gOd6dOG5VCVjEtjwf1ewCzoYoma14wCm5gf8/4mT7FxNZ5zuB4QjBz0rJ6d/iQ2slugGU0syxnXKBLIFh2+Hx7WRA092URN/32owNkFVaj9OAg7pGKP97gdmKcqChaRoRLjgwJXQ5dr1YbLyG0qzhIUA3Vu1PtPb5qWBOykTxh3lO1ylt4Mlo2M9q7QjqONPfwXXOmttB792hOfERKzgniofrq0kwKEl/Yd/QhktOkrbrKUs1Xco0R3365y4kmuIdes3lws17nmWbu6fRZvjGrC6Zu3ZN7QRgxIpRWd1R7g6MkHnOBfz4Ws4yCheJorn5XgmbamnABpo6t1mwZEk7/6\",\n \"d1c3d80bf13fd42b3e8582a15a21172d23b80247f3b6ca85ffa97f018db4dfcb\": \"XldIzkVQ/K0VVG2P0RElCMdWn9S8BZSDOC5aiNVoz6GRXffE8J1looO+ZKsmT2oV8KkcUmbaHnxaWNixFC4Q3LozSkrcAjdnMq0MZxRFk/WVQmsDLTuUFEfKT6s6j9ERT/cTcH7Qw0bzDRzY8H3Q91qdw02RK9C2JWHqWdgxn6G0u8gIPFom7atyZb6eF6wpbPu2y+hYtV+KQ+WKbajiji3RYxBUEmnlsIA7TwaKBNRbL8IbmP0iX8o21yI6BRpPLQ3dfHz1J0FWdnvucbZaN63DLkZVyy0RnJ4jKULGiYXTCkKCFri3kNib799tEsU9JVUq46V5hKdP8SOxarWv7BCYhmvxbH3O53OVEpjmCALaVHtrD/S7R4R7qsPiTk290vk7edzbbj5NyoPsXJPXGDX41OwipCoIL6TMulboWVWU/ZjTSh/bH5WfqPdLJDhKx61AnZSpUPJgJroNdzojjw==\",\n \"e5273e3a62dee1a16328471a471f3a361eee0de07de68a11b6695c8483a4eb88\": \"JlcL/yfbL/d+1bjsMMpNkCN9AoNtQSHiecicfzxrpep2Vzthoooyk3zGGyrStrhmxHU1DgfoyW176op+3stFf4InqNCBBU2dciGuJrfEcmj7GuWi7eWVZWOcsZOrbOc07dz4BkaSxjPfCkxShE123IBOdz3t4Lot1CD0BsE8smVFF3qp6x0SFYxfofNSIU5T2gwIRE3HzJPzN1tnsV98lhaAUU/a3YUofqrgVNPAIrqZSH+bAmdGhxPsAJNzW+cq199LRjHVpIY3n41WoujCzZK3bF3hbRtU5hxQK+zqMQnSskDlk/34arHlTPRW+0z8hIIMcZjq+wzxxLBdcIim9hi/HsQjIrQjS4kdFwHuRZ7Mnjv0c2KLCui36pYqjAaMYhhIh5HOeyorUJY0T9ux08f5jE5caofU2GZu9pHZZjk=\",\n \"e755700c5bcea4905a1a3f900351d8a5564098bc081e6754e379f805728d190a\": \"O51c+F+qBXvwiodQC7gu1Rr+OfdGN4VI8gsLkqaiiGDyS3znAOXh0/0NF4+CVA98s896hwo9fgRMnLqOh47dkQdpFJVqA8GZS7ye9tf9kQIG46IfkhrKYJNoeilTs0rJC9MI9qK23EtTiDEYPjK2nuEvnCx67Wvj8uyUZvJM152LC9Qy/YdXw0uTP0jD/5hLDjOHWgNGEt4lalejai4yPR4xh4aHAZi8J7KNCdr9WnfQeTv9r0g7XVacs7+lOHc83LzyU2TUdgqlCULob6toS6yWMuoCF1Hpva+OTUyNYIHA1tywiItg4/frDOfdx+wS7SmUfTPxwrDgwfYKRZLT8AnBgScna9JdcDGOF3Vx4mM4H7E2RXM8b9hc+hs0XjsJmtmUueCm96U8Yt9Jj0O9eWVB5Huj47cF4HfJA/gBoOESUMGqURVMjvWL/XiX6+epAdNvySvRTtnOZpEfTvQHBDu1l8JzF7mVqCOOmIqWnS8IDVuQlZhcgtCKn00SWDal1i2RbbqeJbYoEQtCWHkwOWi2Uy37gj8mKZBNxMURazE=\"\n },\n \"infohash_v1\": \"59c586143f057d74f28f0c423b0b6e5d39317838\",\n \"infohash_v2\": \"84c40568b01503d8d4baed762ae8aa3c096ce6bdfdc7ede39fe9c6ef531f2de3\",\n \"type\": \"Torrent\"\n}\n"},{"location":"fep/d8c8/#abbreviated-representations","title":"Abbreviated Representations","text":""},{"location":"fep/d8c8/#v1_1","title":"V1","text":"Minimal
{\n \"id\": \"https://example.com/torrents/mytorrent123\",\n \"infohash_v1\": \"1ad02871c78eb1c2934f46de0c7ffd9ef9ee4083\"\n}\n Expanded
{\n \"id\": \"https://example.com/torrents/mytorrent123\",\n \"infohash_v1\": \"1ad02871c78eb1c2934f46de0c7ffd9ef9ee4083\",\n \"bencoded\": \"https://example.com/torrents/mytorrent123.torrent\",\n \"magnet\": \"magnet:?xt=urn:btih:1ad02871c78eb1c2934f46de0c7ffd9ef9ee4083&tr=http%3A%2F%2Fexample.com%2Fannounce\"\n}\n"},{"location":"fep/d8c8/#v2_1","title":"V2","text":"Minimal
{\n \"id\": \"https://example.com/torrents/mytorrent123\",\n \"infohash_v2\": \"d655cc657b0b56975c2596ef1c493055565daccaf2ae29cafdea22bd7cc80e6a\"\n}\n Expanded
{\n \"id\": \"https://example.com/torrents/mytorrent123\",\n \"infohash_v2\": \"d655cc657b0b56975c2596ef1c493055565daccaf2ae29cafdea22bd7cc80e6a\",\n \"bencoded\": \"https://example.com/torrents/mytorrent123.torrent\",\n \"magnet\": \"magnet:?xt=urn:btmh:d655cc657b0b56975c2596ef1c493055565daccaf2ae29cafdea22bd7cc80e6a&tr=http%3A%2F%2Fexample.com%2Fannounce\"\n}\n"},{"location":"fep/d8c8/#hybrid_1","title":"Hybrid","text":"Minimal
{\n \"id\": \"https://example.com/torrents/mytorrent123\",\n \"infohash_v1\": \"1ad02871c78eb1c2934f46de0c7ffd9ef9ee4083\",\n \"infohash_v2\": \"d655cc657b0b56975c2596ef1c493055565daccaf2ae29cafdea22bd7cc80e6a\"\n}\n Expanded
{\n \"id\": \"https://example.com/torrents/mytorrent123\",\n \"infohash_v1\": \"1ad02871c78eb1c2934f46de0c7ffd9ef9ee4083\",\n \"infohash_v2\": \"d655cc657b0b56975c2596ef1c493055565daccaf2ae29cafdea22bd7cc80e6a\",\n \"bencoded\": \"https://example.com/torrents/mytorrent123.torrent\",\n \"magnet\": \"magnet:?xt=urn:btih:1ad02871c78eb1c2934f46de0c7ffd9ef9ee4083&xt=urn:btmh:d655cc657b0b56975c2596ef1c493055565daccaf2ae29cafdea22bd7cc80e6a&tr=http%3A%2F%2Fexample.com%2Fannounce\"\n}\n"},{"location":"fep/d8c8/#reference-implementation","title":"Reference Implementation","text":"A python package with PEP 751 lockfile is included with this FEP, see ./implementation. The package implements encoding and decoding Torrent objects to and from bencoded torrents.
Install the package with python -m pip install . from the FEP directory, and then call fepd8c8 --help to see usage documentation
usage: fepd8c8 [-h] [--encode] [--decode] [--with-context] -i INPUT [-o OUTPUT]\n\nEncoding and decoding torrents to ActivityStreams Torrent objects.\n\noptions:\n -h, --help show this help message and exit\n --encode Encode a .torrent file to JSON\n --decode Decode a JSON torrent object to .torrent\n --with-context Include the JSON-LD Context in the json output\n -i, --input INPUT path to input .torrent or JSON file\n -o, --output OUTPUT path to write output to. If not provided, print to stdout\n Additionally, the torrent-models package will implement export to the specified format following this FEP's drafting (and this FEP will be updated with the version of torrent-models that implements it, when it is released)
Two string encodings (base64 and hexadecimal) are specified for binary data. This choice was made to balance space efficiency with matching conventions that are common in bittorrent clients: bittorrent clients and trackers typically represent infohashes and other hashes as hexadecimal strings, however base64 is a more efficient encoding for the much larger concatenated pieces and piece layers strings.
The handling of strings in the bittorrent specifications is vague, and that vagueness is matched here.
"},{"location":"fep/d8c8/#out-of-scope","title":"Out Of Scope","text":"This FEP only provides a means of representing .torrent files in ActivityPub/ActivityStreams clients. The following is thus out of scope:
Torrent objects from files and directoriesTorrent objects.torrent files, though the nature of RDF and JSON-LD allowing for future extensions is part of the motivation of this FEP.CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/d8c8/#footnotes","title":"Footnotes","text":"A .torrent file is often just called \"a torrent,\" as it is in this document.\u00a0\u21a9
Bittorrent v2 is a backwards compatible extension of bittorrent v1, so torrents may be v1-only, v2-only, or so-called \"hybrid\" torrents that have both v1 and v2 fields.\u00a0\u21a9
Install the package with python -m pip install .
Use the CLI fepd8c8, call fepd8c8 --help for usage instructions
usage: fepd8c8 [-h] [--encode] [--decode] [--with-context] -i INPUT [-o OUTPUT]\n\nEncoding and decoding torrents to ActivityStreams Torrent objects.\n\noptions:\n -h, --help show this help message and exit\n --encode Encode a .torrent file to JSON\n --decode Decode a JSON torrent object to .torrent\n --with-context Include the JSON-LD Context in the json output\n -i, --input INPUT path to input .torrent or JSON file\n -o, --output OUTPUT path to write output to. If not provided, print to stdout\n"},{"location":"fep/d8c8/implementation/#testing","title":"Testing","text":"To validate that we can roundtrip a torrent to/from the JSON representation,
test dependency group: python -m pip install '.[test]'--torrentdir ./some/directory argument that points to a directory with .torrent files to roundtrip--keep-output to inspect the created json filestorrent-models - a more complete implementation of .torrent files in python
DRAFT 2024-07-02 #350 - codeberg"},{"location":"fep/d9ad/#summary","title":"Summary","text":"This is a proposal to enhance the fediverse by creating test cases for FEPs. The proposal describes a Conformance Test Rule format that FEP authors and testers may find useful when creating tests cases as proposed.
"},{"location":"fep/d9ad/#contents","title":"Contents","text":"FEP-a4ed: The Fediverse Enhancement Proposal Process describes a process for proposing enhancements to the fediverse (i.e. 'FEPs').
However, this process says nothing about testing implementations claiming to conform to those proposals nor ways of making FEPs that are more conducive to consistent conformance testing.
However, consistent testing is an important part of an interoperability and accessibility on the web:
In order for web specifications to permit full interoperability and access to all, it is important that the quality of implementation be given as much attention as their development. Moreover, as the complexity of W3C specifications and their interdependencies increases, quality assurance becomes even more important to ensuring their acceptance and deployment in the market
\u2015NIST.gov - W3C Quality Assurance Activity Initiated
The goal of this FEP is to propose a way of proposing FEPs that may lead to more consistent testing of candidate implementations claiming to conform to a FEP.
For more context on conformance, see the appendix on conformance for:
FEPs authors MAY include a conformance clause.
FEPs authors MAY Indicate which conformance requirements are mandatory, which are recommended, and which are optional.
FEP authors MAY write test assertions.
FEP authors and testers MAY write tests.
FEP testers MAY publish a FEP test as a new FEP.
FEP implementors MAY test their implementations.
FEP implementors MAY publish an Implementation Conformance Statement explaining how the implementation conforms to a FEP
Project and services claiming to implement a FEP SHOULD publish an Implementation Conformance Statement
FEP testers MAY write tests using W3C Accessibility Conformance Testing (ACT) Rules Format 1.0
"},{"location":"fep/d9ad/#conformance-tests","title":"Conformance Tests","text":"Conformance Tests provide guidance for developers of automated testing tools and manual testing methodologies, to help ensure consistent interpretation of the FEP.
"},{"location":"fep/d9ad/#w3c-accessibility-conformance-testing-act-rules-format","title":"W3C Accessibility Conformance Testing (ACT) Rules Format","text":"W3C ACT (spec) is a conformance test format that can be used to provide guidance for testing conformance to W3C's Web Content Accessibility Guidelines that are normatively referenced in other standards and laws like section508.gov, gov.co.uk, and Europe's EN 301 549.
While there may be other formats for Conformance Tests, the ACT Format is sufficient for expressing Conformance Tests not only for accessibility, but for FEPs as well. This author was unable to find any other commonly used conformance test formats. At the same time, because the ACT format was designed specifically for accessibility guidelines, the ACT format may be imperfect for FEP Conformance Tests. So this FEP does not specify a strict conformance test format. Instead, it specifies useful components of a Conformance Test, most of which are inspired by similar subcomponents of ACT Rules.
"},{"location":"fep/d9ad/#fep-d9ad-conformance-test-format","title":"FEP-d9ad Conformance Test Format","text":"This FEP defines a kind of Conformance Test that may be used. It reuses many good decisions from the ACT Rule Format, while generalizing the format to be useful for expressing tests for things other than accessibility (e.g. FEPs).
At a high level, a Conformance Test specifies
Each of these components may be a part of a conformance test.
Applicability describes how to determine whether a test is even able to be applied to a particular Input and produce a meaningful outcome other than inapplicable.
A test may have all kinds of outcomes, e.g. inapplicable. If there is a test specified to apply to an ActivityPub Actor Object and check that it is valid JSON, and the test is run with input of a fish, it's not even possible to check the fish for JSON Syntax conformance to derive any targets or apply expectations. It's a fish. The test doesn't apply at all, and in situations like this, the test run MAY have outcome inapplicable. The Applicability section in a Conformance Test specifies how to determine whether the test applies to the test input and, if so, how to derive the test targets.
Inspired by
An example of an Applicability clause for a hypothetical conformance test is:
This test applies to an ActivityPub Actor Object. If the value of the actor input's \"type\" property is an array, there should be a test target for each entry in the array. If it is not an array, there should be one test target whose value is the value of the actor input's \"type\" property.
https://w3id.org/fep/d9ad/ns/test/applicability
"},{"location":"fep/d9ad/#change-log","title":"Change Log","text":"A log with entry for each change to the test over time.
For example, if a test if maintained over many years, it is likely that the editors will learn from experience with implementations and usage of the test implementations that there were unexpected inputs the test might be presented with, and then update the test inputs, target, or expectations to better handle the situation. This kind of update is encouraged, but it should be logged in the Conformance Test's Change Log.
"},{"location":"fep/d9ad/#uri_1","title":"URI","text":"https://w3id.org/fep/d9ad/ns/test/changeLog
"},{"location":"fep/d9ad/#description","title":"Description","text":"A plain language overview of what the test does and why.
Inspired by
This is a conformance test that checks whether an ActivityPub Actor satisfies syntax requirements for the \"type\" property
"},{"location":"fep/d9ad/#uri_2","title":"URI","text":"https://w3id.org/fep/d9ad/ns/test/description
"},{"location":"fep/d9ad/#expectations","title":"Expectations","text":"Assertions about test targets.
An Conformance Test MUST contain one or more expectations.
The expectations describe what the requirements are for the test targets.
An expectation is an assertion about a test target.
Expectations determine test target outcomes
Each expectation must be distinct, unambiguous, and be written in plain language.
Inspired by
https://w3id.org/fep/d9ad/ns/test/expectations
"},{"location":"fep/d9ad/#glossary","title":"Glossary","text":"Definitions for common terms.
The Glossary for a test MUST define each of the test's possible Outcomes.
Inspired by
a globally unique identifier that identifies the Conformance Test
The Identifier MUST be a URI.
Inspired by
urn:uuid:93bafe14-b13f-4a9a-9d47-0a9316d50c97https://bengo.is/fep/d9adhttps://w3id.org/fep/d9ad/ns/test/id
"},{"location":"fep/d9ad/#input","title":"Input","text":"This is what goes in to each run of the conformance test.
The test input is the entirety of how each run of the test can be configured.
An Input may itself have several components.
"},{"location":"fep/d9ad/#example-input-specification","title":"Example Input Specification","text":"Consider a hypothetical Conformance Test that tests conformance with ActivityPub. An Input to the test may have several components, for example:
id - URI - An ActivityPub Object IDauthorization - optional string - A value to pass with each request to fetch idThe Input and its components MAY be represented as a JSON Object with a property for each named component:
"},{"location":"fep/d9ad/#example-input-as-json","title":"Example Input as JSON","text":"{\n \"object\": \"https://bengo.is/fep/d9ad\",\n \"authorization\": \"mellon\"\n}\n"},{"location":"fep/d9ad/#uri_5","title":"URI","text":"https://w3id.org/fep/d9ad/ns/test/input
"},{"location":"fep/d9ad/#issues-list","title":"Issues List","text":"A list of unresolved issues with the test.
Tests should be published early and often, even and especially before there are no known issues with it. The issues may be considered over time, perhaps waiting for enough information to determine a resolution, and then the test may be updated accordingly. The Issues List makes test readers aware of unresolved issues that may lead to test changes later.
Inspired by
https://w3id.org/fep/d9ad/ns/test/issuesList
"},{"location":"fep/d9ad/#name","title":"Name","text":"A short label for the test that can distinguish it in a list of tests.
The name MUST NOT include HTML markup.
The name MAY have distinct values for distinct languages.
The name may be a value that is not unique amongst all other test cases. However, test names should be sufficiently descriptive to distinguish them from other tests in the same test suite.
Inspired by
An example name for a hypothetical test:
ActivityPub Actors MUST have a valid \"type\" property value according to FEP-003c
"},{"location":"fep/d9ad/#uri_7","title":"URI","text":"https://w3id.org/fep/d9ad/ns/test/name
"},{"location":"fep/d9ad/#outcomes","title":"Outcomes","text":"A test's glossary SHOULD define every possible outcome that the test may assign to test targets as a result of the test expectations.
"},{"location":"fep/d9ad/#uri_8","title":"URI","text":"https://w3id.org/fep/d9ad/ns/test/glossary
"},{"location":"fep/d9ad/#requirements-mapping","title":"Requirements Mapping","text":"When a Conformance Test is designed to test conformance to one or more requirements documents, the test must list all requirements from those documents that are not satisfied when one or more of the outcomes of the test is failed.
Each requirement in the mapping must include the following:
Inspired by
https://w3id.org/fep/d9ad/ns/test/requirementsMapping
"},{"location":"fep/d9ad/#target","title":"Target","text":"All parts of the test subject that will be checked by the test.
Each target MUST have a test outcome. Each possible outcome SHOULD be defined in the glossary.
From a given input, the test may derive many targets. For example, from an input that is a JSON Object, a test could derive a test target for each JSON Object nested within the input Object. Then the test, for each target, could check for conformance to a hypothetical requirement that all objects within the input MUST have a certain property.
Inspired by
https://w3id.org/fep/d9ad/ns/test/input
"},{"location":"fep/d9ad/#test-cases","title":"Test Cases","text":"Test Cases (i.e. test cases for the test itself) are a set of example inputs and corresponding results that implementors of the test can check to be confident they have implemented the test has specified in prose. The Test Cases may also be helpful to test readers seeking to understand the intention of the test expectations.
Every Conformance Test Case SHOULD include
Inspired by
https://w3id.org/fep/d9ad/ns/test/testCases
"},{"location":"fep/d9ad/#test-subject","title":"Test Subject","text":"An overview of what kind of thing the Conformance Test tests. For example, a FEP Conformance Test may apply to an ActivityPub Actor, or another ActivityPub Object, or some part of a server that hosts many ActivityPub Objects, or it could test something else entirely. The Test Subject is helpful for contextualizing the test Input
Inspired By
The test suites that the test is a part of, if any.
For each test suite membership, the test should specify:
https://w3id.org/fep/d9ad/ns/test/testSuiteMemberships
"},{"location":"fep/d9ad/#conformance-with-this-specification","title":"Conformance with this Specification","text":"Conformance requirements are indicated by sentences containing MUST a la RFC2119. A FEP-d9ad Conformant Conformance Test is a document satisfying all conformance requirements in this document."},{"location":"fep/d9ad/#related-links","title":"Related Links","text":"These links were helpful when researching conformance testing
Conformance is the fulfillment of specified requirements by a product, process, or service. These requirements are detailed in a specification as part of a conformance clause and in the body of the specification. A conformance clause is the section of a specification that identifies all the criteria that must be satisfied in order to claim conformance to the specification.
\u2015w3.org - QA Framework: Specification Guidelines
"},{"location":"fep/d9ad/#what-is-a-conformance-model","title":"What is a Conformance Model?","text":"What does it mean?
The conformance model is the conceptual framework in which conformance is defined. It consists of and is defined by addressing at least these three topics:
Why care?
The key is to communicate to the reader what conformance to the specification is all about. The model provides a framework for implementers, describes what they need to build in order to conform, and explains the different ways that they could claim conformance. It provides users and customers with a basis on which to express their requirements.
\u2015w3.org - QA Framework: Specification Guidelines
"},{"location":"fep/d9ad/#why-does-conformance-matter","title":"Why does Conformance Matter?","text":"It is inevitable that people (e.g., vendors, purchasers) will either claim conformance or demand conformance to a technology. In fact, claiming conformance to a technology may be required in certain situations. Thus, it is important to provide a consistent and unambiguous way to make these claims. Identification of the specification version, class of products, and conformance label are some of the items that could be part of such wording.
Why care?
Having a framework, by which to make conformance claims for a particular usage of the technology, minimizes confusion by people who are interested in such claims. Many contexts use conformance claims, including legal as part of regulations, laws, or policies and commercial when selling or buying a product.
\u2015w3.org - QA Framework: Specification Guidelines
"},{"location":"fep/d9ad/#copyright","title":"Copyright","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/db0e/","title":"Index","text":"hide: - navigation description: \"This proposal addresses the problem of authenticating access to the content of non-public groups. It is mostly intended to supplement FEP-400e. Only the server that hosts the Group actor knows for sure who can and can not access the content in the group. However, due to each object being hosted on the server of the actor that created it, it is not ordinarily possible for those other servers to restrict access to that object only to those actors who have the permission to see it. This FEP defines an authentication mechanism, \"actor tokens\", that allows an actor to issue tokens that serve as a temporary proof of group membership for other servers.\"
"},{"location":"fep/db0e/#fep-db0e-authentication-mechanism-for-non-public-groups","title":"FEP-db0e: Authentication mechanism for non-public groups","text":"Authors Status Date received Tracking issue Discussions Repository Gregory Klyushnikov activitypub@grishka.meDRAFT 2024-05-03 #313 - codeberg"},{"location":"fep/db0e/#summary","title":"Summary","text":"This proposal addresses the problem of authenticating access to the content of non-public groups. It is mostly intended to supplement FEP-400e.
Only the server that hosts the Group actor knows for sure who can and can not access the content in the group. However, due to each object being hosted on the server of the actor that created it, it is not ordinarily possible for those other servers to restrict access to that object only to those actors who have the permission to see it.
This FEP defines an authentication mechanism, \"actor tokens\", that allows an actor to issue tokens that serve as a temporary proof of group membership for other servers.
"},{"location":"fep/db0e/#requirements","title":"Requirements","text":"The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].
"},{"location":"fep/db0e/#fetching-content-from-the-server-that-hosts-the-group","title":"Fetching content from the server that hosts the group","text":"To fetch an object from the server that hosts the group (including the Group actor itself for private groups), the requesting server MUST sign the GET request with an HTTP signature using the key of any of its actors. It is RECOMMENDED to use a server-wide service actor for this purpose, e.g. /activitypub/serviceActor in Smithereen. The rationale for this is that most ActivityPub servers only fetch and store a single copy of each remote object for all users to whom it may concern, and are responsible themselves for enforcing the visibility rules, if any, either way.
The process of fetching an object from other server involves an actor token. An actor token is a cryptographically signed temporary proof of membership in a group. Since it would be impractical to provide a revocation mechanism, an actor token has a limited validity time in order to account for cases when someone has left a group or was removed from it.
"},{"location":"fep/db0e/#structure-of-the-actor-token","title":"Structure of the actor token","text":"An actor token is a JSON object with the following REQUIRED fields:
issuer: ID of the actor that generated this tokenactor: ID of the actor that the token is issued to (and must be presented with a valid HTTP signature of)issuedAt: timestamp when the token was generated, ISO-8601 instant (same format as ActivityPub timestamps)validUntil: timestamp when the token expires, ISO-8601 instantsignatures: array of signature objects, currently with only one possible, and REQUIRED, element defined:algorithm: must be the string rsa-sha256keyId: key ID, same as in HTTP signatures (e.g. https://example.com/groups/1#main-key)signature: the RSA-SHA256 signature itself encoded as base64, see below for detailssm:actorToken endpoint","text":"Actors that are capable of issuing actor tokens have a sm:actorToken endpoint (where sm is an alias to JSON-LD namespace http://smithereen.software/ns#) in their endpoints object. This endpoint accepts signed GET requests and returns actor tokens.
To use an actor token when fetching an object, pass it as Authorization: ActivityPubActorToken {...} HTTP header.
signature, and transform them into the format key: value. Add these strings to an array.\\n, U+000A).signature). It is RECOMMENDED that the validity period is 30 minutes, and it MUST NOT exceed 2 hours.signature, algorithm, and keyId fields.signatures array.actor in the token object matches the actor ID from keyId in the HTTP signature. Otherwise, you MUST return a 403 and stop.signatures array, find an object that has algorithm set to rsa-sha256 to get the signature value. If there isn't any, you MUST return a 403 and stop.issuedAt MUST be in the past, validUntil MUST be in the future, and the difference between them MUST NOT exceed 2 hours. It is RECOMMENDED to apply some margin to these checks to account for imprecisely set clocks. Smithereen uses 5 minutes.issuer.{\n \"issuer\":\"https://friends.grishka.me/groups/75\",\n \"actor\":\"https://activitypub.academy/actor\",\n \"issuedAt\":\"2024-05-03T14:02:18.680404311Z\",\n \"validUntil\":\"2024-05-03T14:32:18.680404311Z\",\n \"signatures\":[\n {\n \"algorithm\":\"rsa-sha256\",\n \"keyId\":\"https://friends.grishka.me/groups/75#main-key\",\n \"signature\":\"w+W1nNV+XBvXi8sDEUZB7muWSSnv1mEE4tNZJqF5LeoxAstBMiBZi8dtHF+v+vXKVPWBAdZUKLS5CttmgZ4tvnvZAfsBztCjYLyiolVQ71IO2Jxlu00Xo9FDoSTRZ61tXdfWufuzs5lRjG3t+S1t1lLllBFmvPLg6BwmdEPvlZvPYnTJzwNY0ljOjickPqfyvdzIslmdYX6dPC0Ayyi028ZmR2SN1Vooc9vnUQ7GMPrlAZtmXgjCVGw5X/cKlAVvGECxRjJnkKEKiLp3lv/SM1UUhP3VRpBSFhXnRX/1QhTUaFV1MhrfDFgWGPg8ypIf6O/M52+iSpJyIOGepmjmow==\"\n }\n ]\n}\n"},{"location":"fep/db0e/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/dc88/","title":"FEP-dc88: Formatting Mathematics","text":"Authors Status Date received Tracking issue Discussions Repository Calvin Lee pounce@integraldoma.inDRAFT 2023-09-12 #161 - codeberg"},{"location":"fep/dc88/#summary","title":"Summary","text":"This FEP recommends a method for formatting mathematics in ActivityPub post content in [MathML Core]. Furthermore, this FEP describes how to sanitize and convert such mathematics to plain text, if an implementation does not wish to support mathematical formatting.
"},{"location":"fep/dc88/#requirements","title":"Requirements","text":"The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119]. \u201cThe implementation\u201d is to be interpreted as an ActivityPub conformant Client, ActivityPub conformant Server or ActivityPub conformant Federated Server as described in [ActivityPub] which wishes to produce or consume mathematically formatted content.
"},{"location":"fep/dc88/#history","title":"History","text":"Written mathematics depends very heavily on a system of notation which has been continuously evolving over thousands of years. Despite its ubiquity, mathematical notation is anything but constant. Mathematicians rely heavily on complicated typesetting systems such as [LaTeX] to layout text in their desired fashion.
Several fediverse instances, such as [Mathstodon] have emerged to host discussion related to mathematics. This is to no small part due to the difficulty of conveying and formatting mathematical text, and many provide their own typesetting implementation based on [LaTeX]. However, rendering TeX-like formats is expensive and fraught with issues due to security and implementation-specific quirks. This has resulted in multiple instances with incompatible TeX-like implementations.
In recent years, [MathML Core] has become standardized by all major web browsers, and offers an alternative method to communicate mathematics between differing fediverse implementations that is compatible with the ActivityPub standard.
"},{"location":"fep/dc88/#formatting-mathematics","title":"Formatting Mathematics","text":"The implementation MAY produce mathematical formatting in the summary or content properties of [ActivityStreams] objects, as defined in [Activity Vocabulary] if the mediaType is text/HTML (the default). This formatting MUST be placed within one or more top-level <math> elements, hereon referred to as 'a math element'.
A math element MUST contain one <semantics> child element, and no other children. The <semantics> element MUST contain a [MathML Core] expression as its first child, and at least one <annotation> element. The encoding property of this <annotation> element SHOULD be \"application/x-tex\", but MAY be \"text/plain\", and MUST contain a plain-text description of the mathematics\u2014preferably in the authored format. The implementation MAY include additional <annotation> or <annotation-xml> elements with other semantic information.
All elements contained within a math element MUST be MathML Core Elements as defined in [MathML Core], excluding those contained within <annotation> elements.
The implementation SHOULD sanitize incoming mathematical formatting before displaying it to a user. There are two methods by which an implementation may sanitize incoming mathematical formatting.
"},{"location":"fep/dc88/#sanitizing-a-math-element","title":"Sanitizing a math element","text":"The implementation MAY sanitize a math element before displaying it to a user. The implementation SHOULD remove all non MathML Core Elements as defined in [MathML Core]. The implementation MAY remove any attribute which does not contain semantic information (see [Semantic Attributes]). The implementation SHALL NOT remove any [Semantic Attributes] or MathML core Elements and instead should replace a math element with text.
"},{"location":"fep/dc88/#replacing-a-math-element-with-text","title":"Replacing a math element with text","text":"The implementation MAY remove a math element completely, and replace it with text within the <annotation> element with encoding \"application/x-tex\" as described in [Formatting Mathematics] and SHOULD fall back to a \"text/plain\" annotation. If a math element is not formatted as described in [Formatting Mathematics], then the implementation MUST remove it completely.
The implementation MAY surround the text from the <annotation> element with a pair of delimiters. For example, if a math element has the attribute display=\"block\", it may choose the delimiters $$ and $$, and if displaystyle=\"inline\" it may choose $ and $ to match the TeX typesetting system.
{\"@context\": [\"https://www.w3.org/ns/activitystreams\", {\"@language\": \"en\"}],\n \"type\": \"Note\",\n \"id\": \"http://postparty.example/p/2415\",\n \"content\": \"I have a truly marvelous proof that\n <math>\n <semantics>\n <mrow>\n <msup><mi>x</mi><mi>n</mi></msup>\n <mo>+</mo>\n <msup><mi>y</mi><mi>n</mi></msup>\n <mo>\u2260</mo>\n <msup><mi>z</mi><mi>n</mi></msup>\n </mrow>\n <annotation encoding=\\\"application/x-tex\\\">x^n+y^n\\\\ne z^n</annotation>\n </semantics>\n </math>\n which this note is too small to contain!\",\n \"source\": {\n \"content\": \"I have a truly marvelous proof that \\\\(x^n+y^n\\\\ne z^n\\\\) which this note is too small to contain!\",\n \"mediaType\": \"text/markdown+math\"}}\n This object's source content represents a valid sanitization of its content field.
mathvariant normal all displaystyle, scriptlevel all <math> display all <mfrac> linethickness 0, 1 <mspace> width, height, depth all <mo> form stretchy, symmetric, largeop, movablelimits, lspace, rspace, minsize, all"},{"location":"fep/dc88/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/dd4b/","title":"FEP-dd4b: Quote Posts","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.orgDRAFT 2025-02-21 #511 Discussions codeberg"},{"location":"fep/dd4b/#summary","title":"Summary","text":"This FEP describes the mechanism defined in Activity Streams 2.0 and the Activity Vocabulary for making quote posts, that is, Announce activities with additional commentary.
"},{"location":"fep/dd4b/#motivation","title":"Motivation","text":"Redistributing content or activities created by other actors is a key activity on the social web. The Announce activity type is defined in Activity Streams 2.0 (\"AS2\") to represent this activity. Announce is used in ActivityPub to provide sharing functionality; see 7.11 Announce Activity (sharing).
The Activity object type in AS2 inherits all the properties of the Object type. This means that all activity types have a rich set of properties for representation as first-class content on the social web. The Announce type is no exception.
However, the ActivityPub specification does not describe how to use those properties to provide additional commentary, metadata, and files for the shared content. Called \"quote Tweets\", \"repost with comment\", or \"quote posts\", this kind of extended sharing gives additional context to shared objects.
This FEP describes how to use the content property to provide this commentary, as well as other properties that may be useful in this context. It is applicable to uses of Activity Streams 2.0 in general; where it applies to ActivityPub, it is noted.
object property of an Announce activity MUST be a reference to the shared content. It MAY be an AS2 Object or a Link, either as a JSON object or as a URL.content property of an Announce activity MAY be used to provide additional commentary on the shared content.attachment property of an Announce activity MAY be used to provide additional media content related to the shared content or the commentary.tag property of an Announce activity MAY be used to provide additional metadata, such as Mention objects or Hashtag objects, about the shared content or the commentary.inReplyTo property of an Announce activity MAY be used to connect an Announce activity to another object as a reply. The replied-to object MAY be the shared content, but this is unusual; it MAY also be part of a separate conversation.Announce activity with an inReplyTo property SHOULD be included in the replies collection of the object being replied to.Announce activities SHOULD be counted as part of the shares collection of the shared content, regardless of any additional properties, as defined in 7.11 Announce Activity (sharing).This represents a simple quote post, where the actor is sharing a note from another actor with commentary.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/activities/aaabbbccc\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/users/evan\",\n \"to\": \"https://example.com/users/evan/followers\",\n \"object\": {\n \"id\": \"https://example.com/notes/1234\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/users/franklin\"\n },\n \"content\": \"I think that this is a good point and should be shared.\"\n}\n"},{"location":"fep/dd4b/#quote-post-with-attachment","title":"Quote Post with Attachment","text":"This represents a quote post with an attachment, where the actor is sharing a note from another actor with commentary and an image.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/activities/dddeeefff\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/users/evan\",\n \"to\": \"https://example.com/users/evan/followers\",\n \"object\": {\n \"id\": \"https://example.com/notes/1234\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/users/franklin\"\n },\n \"content\": \"The author describes the rock formations of Crete; here's an example from my recent visit.\",\n \"attachment\": {\n \"type\": \"Link\",\n \"mediaType\": \"image/jpeg\",\n \"url\": \"https://example.com/images/1234.jpg\"\n }\n}\n"},{"location":"fep/dd4b/#quote-post-with-hashtag","title":"Quote Post with Hashtag","text":"This represents a quote post with a hashtag, where the actor is sharing a note from another actor with commentary and a hashtag.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/activities/ghhiijjkk\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/users/evan\",\n \"to\": \"https://example.com/users/evan/followers\",\n \"object\": {\n \"id\": \"https://example.com/notes/1234\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/users/franklin\"\n },\n \"content\": \"Great description of Cretan geology; saving it for my next trip. <a href='https://example.com/tags/evanstriptocrete'>#evanstriptocrete</a>\",\n \"tag\": {\n \"type\": \"Hashtag\",\n \"href\": \"https://example.com/tags/evanstriptocrete\",\n \"name\": \"evanstriptocrete\"\n }\n}\n"},{"location":"fep/dd4b/#quote-post-with-mention","title":"Quote Post with Mention","text":"This represents a quote post with a mention, where the actor is sharing a note from another actor with commentary and a mention.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/activities/lllmmnnoo\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/users/evan\",\n \"to\": [\"https://example.com/users/evan/followers\", \"https://example.com/users/jeff\"],\n \"object\": {\n \"id\": \"https://example.com/notes/1234\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/users/franklin\"\n },\n \"content\": \"<a href='https://example.com/users/jeff'>@jeff</a> you might like this Cretan geology article.\",\n \"tag\": {\n \"type\": \"Mention\",\n \"href\": \"https://example.com/users/jeff\",\n \"name\": \"jeff\"\n }\n}\n The author of the quoted material can also be mentioned.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/activities/pppqqqrrr\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/users/evan\",\n \"to\": [\"https://example.com/users/evan/followers\", \"https://example.com/users/franklin\"],\n \"object\": {\n \"id\": \"https://example.com/notes/1234\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/users/franklin\"\n },\n \"content\": \"<a href='https://example.com/users/franklin'>@franklin</a> wrote this great Cretan geology article.\",\n \"tag\": {\n \"type\": \"Mention\",\n \"href\": \"https://example.com/users/franklin\",\n \"name\": \"franklin\"\n }\n}\n"},{"location":"fep/dd4b/#quote-post-as-reply","title":"Quote Post as Reply","text":"A quote post can be used as a reply, often to share evidence or information from another source.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/activities/rrrsssttt\",\n \"type\": \"Note\",\n \"actor\": \"https://example.com/users/jeff\",\n \"to\": \"https://example.com/users/jeff/followers\",\n \"content\": \"Does anyone know where I can find a good article on Cretan geology?\",\n \"replies\": {\n \"id\": \"https://example.com/activities/rrrsssttt/replies\",\n \"type\": \"Collection\",\n \"totalItems\": 1,\n \"items\": [\n {\n \"id\": \"https://example.com/activities/lllmmnnoo\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/users/evan\",\n \"object\": {\n \"id\": \"https://example.com/notes/1234\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/users/franklin\"\n },\n \"content\": \"<a href='https://example.com/users/jeff'>@jeff</a> you might like this Cretan geology article.\",\n \"tag\": {\n \"type\": \"Mention\",\n \"href\": \"https://example.com/users/jeff\",\n \"name\": \"jeff\"\n },\n \"inReplyTo\": \"https://example.com/activities/rrrsssttt\"\n }\n ]\n }\n}\n"},{"location":"fep/dd4b/#user-interface-guidance","title":"User interface guidance","text":"A common representation of quote posts is to include the object of the Announce as an embedded card or other representation, with the content of the Announce object as introductory text.
For all Announce activities, it's important to consider how much of the shared content to expose to the recipient of the Announce activity. Using an URL as a reference, or including a limited set of metadata as in the examples in this FEP, allows the publishing server for the shared content to control access to the content. See Inclusion of the shared object in the ActivityPub Primer for more information.
The creator of the quoted content can indicate their consent to be quoted using the shares collection. If the creator consents to being quoted, they can add the quote post to the shares collection. If they do not consent, they can omit the quote post or remove it.
Servers SHOULD provide affordances to allow creators to add to, and remove from, the shares collection. Servers may provide opt-in consent, where Announce activities are not added to the shares collection unless the original author explicitly adds them. The server MAY provide opt-out consent, where Announce activities are added to the shares collection automatically on receipt, but can be removed by the original author.
Consumers of quote posts can refer to the shares collection at any time to determine if the publisher has expressed consent. Consent may be extended or withdrawn at any time.
Consumers SHOULD indicate if the original author has consented to the quote post, and MAY obscure or hide quote posts that do not have the original author's consent.
"},{"location":"fep/dd4b/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/e229/","title":"FEP-e229: Best practices for extensibility","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT 2024-04-02 #293 - codeberg"},{"location":"fep/e229/#summary","title":"Summary","text":"Current popular implementations of ActivityPub do not handle extensibility very well. This FEP seeks to highlight some basic requirements for extensibility, and offer suggested advice to implementers who wish to avoid compatibility issues, particularly for LD-unaware consumers.
"},{"location":"fep/e229/#general-recommendations","title":"General recommendations","text":""},{"location":"fep/e229/#ld-unaware-consumers","title":"LD-unaware consumers","text":""},{"location":"fep/e229/#normalize-types-into-type-sets","title":"Normalize types into type-sets","text":"It is an unfortunate and erroneous belief that objects in [AS2-Core] or [AP] can have only one type. This assumption breaks proper extensibility. Wherever a generic ActivityStreams consumer needs to know whether it is dealing with an [AS2-Vocab] type or [AS2-Core] mechanism like Collections, it cannot do so unless that type is present in the type set. However, extension vocabularies may need to declare additional types as interfaces that have been fulfilled by the given object. For this reason, LD-unaware consumers doing type checks need to take care to normalize type into a set, and check that their desired type is contained within that set.
For example, \"type\": \"Collection\" would be normalized into \"type\": [\"Collection\"].
LD-unaware consumers MUST NOT attempt naive string comparison against the JSON-LD context declaration. There are several possible reasons why a received document might be valid AS2 but not declare a @context. One possibility is that the declared Content-Type is application/activity+json and the producer is LD-unaware. Another possibility is that the producer is LD-aware, but using a different context IRI that defines the same terms. Yet another possibility is that the producer is embedding inline term definitions. Regardless of the reason, either the consumer understands it or does not understand it.
Given that including the [AS2-Context] is only a SHOULD and not a MUST, it is possible for some LD-unaware producers to serialize a document without a @context declaration. If the Content-Type is application/activity+json, then you MUST assume or inject the [AS2-Context] into the document, per [AS2-Core] section 2.1.
By default, the [AS2-Context] document declares @vocab to be _:, meaning that the default vocabulary namespace is the blank namespace. Extension types and properties can be implemented as-is by LD-unaware producers, and the JSON-LD expansion algorithm will expand term to _:term. JSON-LD compaction will not strip these properties, but without the @vocab: _: declaration, they would be stripped. This may be sufficient for experimental or implementation-specific terms that are not expected to be used by anyone else, but it is bad practice for extensibility; the use of the blank namespace for properties is obsolete and may be removed in a future version of JSON-LD.
Compact IRI prefixes can have multiple terms map to the same prefix, depending on which context the producer uses for compaction. For example, say we have a prefix for http://example.com/. You may encounter some documents with example:term, some documents with ex:term, some documents with http://example.com/term, and so on. LD-aware consumers can \"simply\" apply JSON-LD expansion to make all terms unambiguous, and then apply JSON-LD compaction against their local preferred context. LD-unaware consumers instead have to deal with unbounded possible equivalent terms, and will either have to add support for them on a case-by-case basis, or reinvent and reimplement JSON-LD expansion. This issue can be ameliorated by taking care to reuse existing conventional prefixes. An example of this is the [RDFa-Context] \"initial context\".
Since JSON-LD expanded form is unambiguous, it may be a good idea to use it wherever possible. This slightly reduces human readability due to the additional verbosity, but it results in exactly one possible representation of your extension data. LD-unaware consumers will possibly have to learn the structure of JSON-LD expanded form. LD-aware consumers can \"simply\" re-compact the document against any additional contexts they understand.
For example, consider the current use of \"profile fields\" prior to [FEP-fb2a] \"Actor metadata\". Ignoring that Mastodon currently uses sc as a term prefix for an incorrect definition, such a term prefix would be unnecessary if partially-uncompacted JSON-LD was used:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/~alyssa\",\n \"type\": \"Person\",\n \"name\": \"Alyssa P. Hacker\",\n \"attachment\": [\n {\n \"type\": \"http://schema.org/PropertyValue\",\n \"http://schema.org/name\": \"Pronouns\",\n \"http://schema.org/value\": \"she/her\"\n }\n ]\n}\n In general, try to consider whether your target consumer is expected to understand the context you are declaring. For ActivityStreams-specific consumers, the [AS2-Context] is a requirement, and so it can be generally depended upon. Some specifications (such as [WebAnnotations]) may similarly require their own context to be declared, whereas some specifications may not require this. In general, it may be better to not require a context and to use only the partially-compacted form; this is because multiple context declarations makes it possible for some contexts to conflict, and the most recently-declared context will win out, leading to potentially undefined behavior. This behavior can be avoided by being more judicious about your context declarations and your choice of context documents to compact against.
"},{"location":"fep/e229/#declare-the-activitystreams-context-last-if-compacting-against-additional-contexts","title":"Declare the ActivityStreams context last, if compacting against additional contexts","text":"Because [AP] and [AS2-Core] mandate compaction against the [AS2-Context] while also mandating that terms cannot be overridden, it is best to have the [AS2-Context] be the most-recently-declared context. For example:
{\n \"@context\": [,\n \"https://schema.org\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n // ...\n}\n"},{"location":"fep/e229/#how-to-define-an-extension","title":"How to define an extension","text":"LD-unaware producers need to be aware of at least some of the ways that JSON-LD works; otherwise, their extensions will go into a blank namespace and may be stripped in future versions of JSON-LD. See the recommendations for LD-unaware producers above.
"},{"location":"fep/e229/#extension-property","title":"Extension property","text":"Extension properties will primarily be one of two kinds:
@value.@id.For LD-unaware producers, it is enough to produce JSON of the following form:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"http://example.com/valueProperty\": \"some string or number or boolean\",\n \"http://example.com/idProperty\": {\n \"@id\": \"https://example.com/some-resource\"\n }\n}\n For LD-aware producers, first note that compacting against any additional context will make parsing harder for LD-unaware consumers, as described in above guidance for LD-aware producers -- any additional context you declare must be shared by the consumer, and the only guaranteed context is the ActivityStreams context. Still, for the benefit of LD-aware consumers, you SHOULD at least make a context document available for download out-of-band. One mechanism for doing so is described in [FEP-888d].
The previous example can be expressed as follows when you compact against additional context:
{\n \"@context\": [\n {\n \"valueProperty\": \"http://example.com/valueProperty\",\n \"idProperty\": {\n \"@id\": \"http://example.com/idProperty\",\n \"@type\": \"@id\"\n }\n },\n \"https://www.w3.org/ns/activitystreams\"\n ]\n}\n More complete guidance on term definitions is available within [JSONLD11-TERMS].
"},{"location":"fep/e229/#extension-type","title":"Extension type","text":"Extension types follow similar logic to extension properties with literal values.
For LD-unaware producers, it is enough to use a full IRI as an extension type, although note that some vocabularies have been declared as term prefixes as part of the [AS2-Context], and thus MUST be serialized as compact IRIs using the given prefix. For example, vcard is declared by the [AS2-Context], but schema is not. Thus:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": [\"Person\", \"http://schema.org/Person\", \"vcard:Individual\"]\n}\n LD-aware producers may wish to declare additional context, keeping in mind above guidance for LD-aware producers:
{\n \"@context\": [,\n \"https://schema.org\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": [\"Person\", \"schema:Person\", \"vcard:Individual\"]\n}\n Alternatively, to avoid importing the entire Schema.org context:
{\n \"@context\": [\n {\n \"schema\": \"http://schema.org/\"\n },\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": [\"Person\", \"schema:Person\", \"vcard:Individual\"]\n}\n"},{"location":"fep/e229/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/e232/","title":"FEP-e232: Object Links","text":"Authors Status Date received Date final Tracking issue Discussions Repository silverpill @silverpill@mitra.socialFINAL 2022-08-01 2023-12-03 #14 Discussions codeberg"},{"location":"fep/e232/#summary","title":"Summary","text":"This document proposes a way to represent text-based links to ActivityPub objects which are similar to mentions. One example of such link is inline quote within the value of the content property, but this proposal is not limited to any particular use case.
The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/e232/#object-links","title":"Object links","text":"It is expected that software will allow users to define object links using some kind of microsyntax, similar to @mention and #hashtag microsyntaxes. The exact way of defining object links may vary depending on the use case and is out of scope of this document.
If an object's name, summary, or content has qualified links to other objects, that object SHOULD have the tag property, where each object link is represented as a Link object, as suggested by Activity Vocabulary. The properties of this Link object are:
type (REQUIRED): the type MUST be Link or a subtype.mediaType (REQUIRED): the media type MUST be application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\". This specification only deals with ActivityPub objects but in practice the media type can be different and servers MAY accept object links which do not comply with the requirement. For example, a media type of application/activity+json SHOULD be treated as equivalent.href (REQUIRED): the href property MUST contain the URI of the referenced object.name (OPTIONAL): the name SHOULD match the microsyntax used in object's content.rel (OPTIONAL): if relevant, the rel SHOULD specify how the link is related to the current resource. Using rel can provide additional purpose to object links by signaling specific intended use-cases.(This section is non-normative.)
A link to an issue in a bug tracker:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Note\",\n \"content\": \"The bug was reported in #1374\",\n \"tag\": [\n {\n \"type\": \"Link\",\n \"mediaType\": \"application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"\",\n \"href\": \"https://forge.example/tickets/1374\",\n \"name\": \"#1374\"\n }\n ]\n}\n An inline quote:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Note\",\n \"content\": \"This is a quote:<br>RE: https://server.example/objects/123\",\n \"tag\": [\n {\n \"type\": \"Link\",\n \"mediaType\": \"application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"\",\n \"href\": \"https://server.example/objects/123\",\n \"name\": \"RE: https://server.example/objects/123\"\n }\n ]\n}\n Note that the content includes the RE: <url> microsyntax but consuming implementations are not required to parse that in order to make the appropriate associations.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/e3e9/","title":"Index","text":"hide: - navigation description: \" \"All problems in computer science can be solved by another level of indirection\" (the \"fundamental theorem of software engineering\") -- Attributed to Butler Lampson (src) This FEP introduces an ID scheme for ActivityPub objects and collections that has the following properties IDs remains stable across domain migrations. That is, allows the controller of the objects to change object hosting providers without changing the object IDs. IDs are regular HTTP(S) URLs that are resolvable via an HTTP GET request (provided the client allows following 302 redirects). The proposed mechanism identifies objects by adding query parameters to existing Actor profile URLs. ActivityPub clients wishing to fetch the objects make an HTTP GET request to this URL, as usual, carrying whatever authentication mechanism is required currently, and then follow the HTTP 302 status code redirect in the response to the current storage location of the object. Example Actor-Relative URL https //alice-personal-site.example/actor?service=storage&relativeRef=/AP/objects/567 An AP client, encountering an Object ID with this URL makes an HTTP GET request just as it would with any other Object ID http GET /actor?service=storage&relativeRef=/AP/objects/567 HTTP/1.1 Host alice-personal-site.example The server responds with a 302 redirect (which all HTTP clients are able to automatically follow) pointing to the current storage location of the object. For example http HTTP/1.1 302 Found Location https //storage-provider.example/users/1234/AP/objects/567 This redirection mechanism is enabled in all existing HTTP clients by default (see https //developer.mozilla.org/en-US/docs/Web/API/Request/redirect), and requires no additional re-tooling of ActivityPub client code.\"
"},{"location":"fep/e3e9/#fep-e3e9-actor-relative-urls","title":"FEP-e3e9: Actor-Relative URLs","text":"Authors Status Date received Tracking issue Discussions Repository Dmitri Zagidulin @dmitri@social.coop, bumblefudge bumblefudge@learningproof.xyzDRAFT 2024-06-29 #346 - codeberg"},{"location":"fep/e3e9/#summary","title":"Summary","text":"\"All problems in computer science can be solved by another level of indirection\" (the \"fundamental theorem of software engineering\")
-- Attributed to: Butler Lampson (src)
This FEP introduces an ID scheme for ActivityPub objects and collections that has the following properties:
GET request (provided the client allows following 302 redirects).The proposed mechanism identifies objects by adding query parameters to existing Actor profile URLs. ActivityPub clients wishing to fetch the objects make an HTTP GET request to this URL, as usual, carrying whatever authentication mechanism is required currently, and then follow the HTTP 302 status code redirect in the response to the current storage location of the object.
Example Actor-Relative URL:
https://alice-personal-site.example/actor?service=storage&relativeRef=/AP/objects/567
An AP client, encountering an Object ID with this URL makes an HTTP GET request just as it would with any other Object ID:
GET /actor?service=storage&relativeRef=/AP/objects/567 HTTP/1.1\nHost: alice-personal-site.example\n The server responds with a 302 redirect (which all HTTP clients are able to automatically follow) pointing to the current storage location of the object. For example:
HTTP/1.1 302 Found\nLocation: https://storage-provider.example/users/1234/AP/objects/567\n This redirection mechanism is enabled in all existing HTTP clients by default (see https://developer.mozilla.org/en-US/docs/Web/API/Request/redirect), and requires no additional re-tooling of ActivityPub client code.
"},{"location":"fep/e3e9/#actor-relative-urls-for-objects-and-collections","title":"Actor-Relative URLs for Objects and Collections","text":"On the Client side, the main change required is in the author/controller validation procedure (since retrieving the objects at Actor-Relative URLs requires no additional change beyond ensuring that following HTTP redirects is not disabled).
On the Server side (specifically, the server hosting the Actor profile), two changes are required:
service section to the Actor profile, which is required for author/controller validation.302 redirect responses when an Actor profile request is made that has the required query parameters (service and relativeRef params).In addition:
Given the following example Actor profile:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/did/v1\"\n ],\n \"service\": [{\n \"id\": \"https://alice-personal-site.example/actor#storage\",\n \"serviceEndpoint\": \"https://storage-provider.example\"\n }],\n // Rest of the Actor profile goes here\n}\n When fetching an ActivityPub Object or Collection identified by an Actor-Relative URL (that is, when the Object or Collection ID contains the URL query parameters service and relativeRef), a client MUST validate that the server hosting the Object is authorized by the Actor profile:
GET request on the Object or Collection, as usual, including any currently required authorization headers.GET request MUST be able to support HTTP redirection. For example, if using the WHATWG fetch API, the request's redirect property cannot be set to error.Location header of the 302 response (this behavior is the default in most HTTP clients).Location header of the redirect response; for example, if using the WHATWG fetch API, this is the last URL in the response's URL list, retrievable by accessing response.url.actor or attributedTo property).The Client extracts the value of the authorized storage endpoint from the profile:
a. The Client checks to see if the Actor profile contains the service property. b. If the service property is found, the Client searches through the array of service endpoints until it finds a service endpoint with the relative id ending in #storage (note: this is what the service=storage query parameter refers to, in the Actor-Relative URL). The Client extracts the serviceEndpoint property of this service description object. This is the authorized storage endpoint. c. If no authorized storage endpoint is specified in the Actor profile (that is, if the Actor profile does not contain the service property, or if the service property is null or an empty array, or if the service array does not contain a service endpoint object with a relative id that ends in #storage, or if that service endpoint does not contain a serviceEndpoint property containing a URL), the Client SHOULD indicate to the user that the provenance of this Object cannot be determined, or that the storage location of the Object has not been authorized by the profile of the claimed author/controller.
The Client MUST validate that the current URL of the object is authorized by the Actor's profile by checking that:
a. The Object's currentURL starts with the value of the authorized storage endpoint. b. The Object's currentURL ends with the value of the relativeRef query parameter. c. For example, in JS pseudocode, using string concatenation: response.url === (authorizedStorageEndpoint + query.relativeRef) d. If these checks fail (if the current URL of the object is not equal to the string concatenation of the authorized storage endpoint and the relativeRef query parameter), the Client SHOULD indicate to the user that the provenance of this Object cannot be determined, or that the storage location of the Object has not been authorized by the profile of the claimed author/controller.
This validation procedure establishes a two-way link: from the Object to its author/controller Actor profile (via the Object's actor or attributedTo property), and from the Actor profile to the authorized storage service provider, at whose domain the Object is currently stored.
An ActivityPub client conforming to this FEP:
GET mechanism that it currently does.service and relativeRef query parameters.302 redirect in the response.On the server side (specifically, the server hosting the Actor profile), an ActivityPub server conforming to this FEP:
https://alice-personal-site.example/actor), examine the HTTP QUERY parameters. If the service and relativeRef query parameters are present in the request, treat this as an Actor-Relative URL Request (by following the steps below).Examine the Actor profile object for this request. If the profile does not contain a valid serviceEndpoint that corresponds to the service query parameter, the server MUST return a 422 Unprocessable Entity HTTP status code error. To determine whether the profile contains a valid service endpoint:
If the Actor profile does not contain a top level service property: INVALID
service property, but its value is null or []: INVALIDSearch through the array of service endpoints (the value of the service) property, until you find a service object with the id that ends in <actor profile url>#<contents of the 'service' query param>. See sample Actor profile and request below. If no valid service endpoint is found: INVALID
Assuming that a matching service endpoint is found, compose a current location URL from the serviceEndpoint contained in the profile concatenated with the contents of the relativeRef query parameter (see below for example).
Return a 302 Found HTTP status code response, and set the Location response header to the value of the current location URL composed in the previous step. Note: Servers SHOULD NOT return a 301 status response (a 301 response implies a permanent relocation, and the whole point of this FEP is that Actor-Relative URLs are changeable at any point). Similarly, servers SHOULD not return a 303 See Other status response.
Example request URL:
GET https://alice-personal-site.example/actor?service=storage&relativeRef=/AP/objects/567\n The query parameters would be parsed on the server side as something similar to:
{ \"service\": \"storage\", \"relativeRef\": \"/AP/objects/567\" }\n Example Actor profile at that URL:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/did/v1\"\n ],\n \"service\": [{\n \"id\": \"https://alice-personal-site.example/actor#storage\",\n \"serviceEndpoint\": \"https://storage-provider.example\"\n }],\n // Rest of the Actor profile goes here\n}\n Example current location URL (from concatenating the serviceEndpoint value with the relativeRef query parameter): https://storage-provider.example/AP/objects/567
Example response from the server:
HTTP/1.1 302 Found\nLocation: https://storage-provider.example/AP/objects/567\n"},{"location":"fep/e3e9/#object-storage-migration-using-actor-relative-urls","title":"Object Storage Migration Using Actor-Relative URLs","text":"Actor-Relative URLs can be used as an option for portable Object and Collection IDs that remain unchanged even through migrating to a different object hosting provider (as long as the Actor ID remains constant).
"},{"location":"fep/e3e9/#example-storage-provider-migration","title":"Example Storage Provider Migration","text":"Before migration, Alice uses the https://old-storage-provider.example as a storage provider for her AP objects. She makes sure https://old-storage-provider.example is specified as a service endpoint in her Actor profile.
GET https://alice-personal-site.example/actor
returns
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/did/v1\"\n ],\n \"id\": \"https://alice-personal-site.example/actor\",\n \"type\": \"Person\",\n \"service\": [{\n \"id\": \"https://alice-personal-site.example/actor#storage\",\n \"serviceEndpoint\": \"https://old-storage-provider.example\"\n }],\n \"assertionMethod\": { /* \u2026 */ },\n // All the other profile properties \u2026\n}\n Alice then creates a Note and stores it with the storage provider (making sure to add an Object Identity Proof). Example request:
POST /AP/objects/\nHost: old-storage-provider.example\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Note\",\n \"content\": \"This is a note\",\n \"attributedTo\": \"https://alice-personal-site.example/actor\",\n \"id\": \"https://alice-personal-site.example/actor?service=storage&relativeRef=/AP/objects/567\"\n}\n returns
HTTP 201 Created\nLocation: https://old-storage-provider.example/AP/objects/567\n Note that this created Object can now be fetched at TWO different URLs:
https://old-storage-provider.example/AP/objects/567https://alice-personal-site.example/actor?service=storage&relativeRef=/AP/objects/567When it comes time to migrate to a different service provider, the new one being located at https://brand-new-storage.example, Alice performs the following steps.
She updates her Actor profile service endpoint, to point to the new provider, so that it looks like this:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/did/v1\"\n ],\n \"id\": \"https://alice-personal-site.example/actor\",\n \"type\": \"Person\",\n \"service\": [{\n \"id\": \"https://alice-personal-site.example/actor#storage\",\n \"serviceEndpoint\": \"https://brand-new-storage.example\"\n }],\n \"assertionMethod\": { /* \u2026 */ },\n // All the other profile properties \u2026\n}\n Note that the serviceEndpoint is the only property in the Actor profile that has to change during migration.
Alice then transfers her Object to the new provider (for this example, she'll be transferring the object individually, though in future FEPs, we expect specification of APIs to transfer all of the objects in one's storage):
POST /AP/objects/\nHost: brand-new-storage.example\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Note\",\n \"content\": \"This is a note\",\n \"attributedTo\": \"https://alice-personal-site.example/actor\",\n \"id\": \"https://alice-personal-site.example/actor?service=storage&relativeRef=/AP/objects/567\"\n}\n returns:
HTTP 201 Created\nLocation: https://brand-new-storage.example/AP/objects/567\n Notice that the object being stored at the new provider is byte-for-byte identical to the object hosted at the old provider; its indirect id and contents do not change.
Throughout this service provider migration, the external indirect id of the object does not change, for the purposes of all other AP mechanisms such as Inbox delivery, Likes and Reposts, and so on.
FEP-8b32: Object Integrity Proofs
Christine Lemmer Webber, Jessica Tallon, [ActivityPub][AP], 2018
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/e965/","title":"Index","text":"hide: - navigation description: \"This FEP normatively specifies exactly one narrow step in almost all the migration user-stories defined in FEP-73cd User Migration Stories the updates to an Actor object made after a migration and/or deactivation event, and the Move activity which a source server propagates to inform followers of said Actor object update Our proposal clarifies semantics and behavior of the earlier FEP-7628 on which it strictly relies. It also proposes a simple, additive approach to use the above to express \"deactivated\" Actors by \"tombstoning\" their Actor objects, i.e. adding \"Tombstone\" to their type array (already afforded by the Activity Streams vocabulary). It also accomodates migrations to new forms of Actor object, such as \"Nomadic\"-style Portable Actors as described in FEP-ef61 Portable Objects and \"Independently-hosted\" Actor objects as described in FEP-7952, both for conforming and non-conforming consumers. As such, fully implementing all optional features of this proposal would require implementing FEP-521a Representing actor's public keys, which adds terms to the Actor object for publishing a verification method to verify assertions about the Actor independently of domain.\"
"},{"location":"fep/e965/#fep-e965-move-activity-for-migrations-and-announce-activity-for-tombstone-events","title":"FEP-e965: Move Activity for Migrations and Announce Activity for Tombstone Events","text":"Authors Status Date received Tracking issue Discussions Repository bumblefudge bumblefudge@learningproof.xyz, bengo @bengo@social.coopDRAFT 2024-07-05 #352 Discussions codeberg"},{"location":"fep/e965/#summary","title":"Summary","text":"This FEP normatively specifies exactly one narrow step in almost all the migration user-stories defined in FEP-73cd: User Migration Stories:
Our proposal clarifies semantics and behavior of the earlier FEP-7628 on which it strictly relies. It also proposes a simple, additive approach to use the above to express \"deactivated\" Actors by \"tombstoning\" their Actor objects, i.e. adding \"Tombstone\" to their type array (already afforded by the Activity Streams vocabulary). It also accomodates migrations to new forms of Actor object, such as \"Nomadic\"-style Portable Actors as described in FEP-ef61: Portable Objects and \"Independently-hosted\" Actor objects as described in FEP-7952, both for conforming and non-conforming consumers. As such, fully implementing all optional features of this proposal would require implementing FEP-521a: Representing actor's public keys, which adds terms to the Actor object for publishing a verification method to verify assertions about the Actor independently of domain.
Migration is currently supported in a somewhat ad hoc and piecemeal way, without harmonized expectations for how to update, announce, or interpret an Actor object after a migration, or after a deactiviation. Deactivation is sometimes referred to as a \"tombstone\" event, both in general usage in distributed systems and in the Activity Streams sense of the Tombstone object type. Adding the \"Tombstone\" member to the type array of an Actor object marks it as deactivated, as is already possible but more commonly implemented for deleted content or Activities than for Actors. We did not do a review of existing codebases, and the only public prior art that we're aware of is the retrospecification of current practice in FEP-7628: Move Actor.
Beyond passively leaving a Tombstone hint for future queries, there have been no public proposals to our knowledge specifying how to actively express a given Actor's controller expressing an \"intent [for that Actor] to be forgotten\" to other servers where interactions with it may be stored, much less how to document that intent for legal purposes (which is explicitly out of scope here).
MUST, MAY, and SHOULD used in the RFC-2119 sense where they appear in CAPITAL LETTERS. Similarly, the references to \"valid\" URIs throughout should be interpreted as conforming to both RFC-3987 and the [Activity Streams guidance on URI usage][Activity Streams URIs].
Implementations SHOULD signal their support for this specification by including \"https://w3id.org/fep/7628\" in the @context array of their Actors, as this will clearly signal that the ABSENCE of a movedTo or copiedTo property indicates a currently-active Actor.
Implementations MAY prove support for this specification by publishing a Conformance Report referencing the tests run. A specification for possible tests is provided in fep-e965-test-case.
"},{"location":"fep/e965/#actor-object-migration-and-deactivation-syntax","title":"Actor Object Migration and Deactivation Syntax","text":"In the section, \"Move Activity\" of FEP-7628, two variations of the Mastodon-style Move Activity are defined, as well as semantics for the movedTo and copiedTo properties that MUST be applied to the Actor object on the source server of the activity:
If previous primary actor is deactivated after migration, it MUST have movedTo property containing the ID of the new primary actor. [...] If previous primary actor is not deactivated, copiedTo property MUST be used.
We add a few more explicit requirements:
movedTo MUST be a string or an array containing 1 string.copiedTo MUST be a string or an array of strings.movedTo and copiedTo MUST NOT be present in the same Actor object.Many other current and future process and Activities could also be using the same semantics, including new \"styles\" or \"profiles\" of the many possible Actor objects allowed by the [ActivityPub] specification. These include Actors that do not change id after migrating, whether they conform to the Nomadic Actor extension, or to the separately-hosted Actor extension. If an account is moving to one of these configurations, the movedTo or copiedTo value will be the id and location of an ap:// URL, or to a URL controlled by the Actor object's data subject, respectively.
If the Actor object before the deactivation event included a public key for signing Activities expressed according to Client-Signing, and the same public key will NOT be published at the destination server for verifying post-migration Activities, then the source server MAY add an expires key and current-timestamp value to the key's assertionMethod object as described in section #2.3.1: Verification Methods of the W3C Data Integrity specification (to which FEP-521a normatively refers). Any consumer fetching this assertionMethod object for the purposes of verifying signatures according to the Data Integrity algorithm will thus invalidate signatures newer than the deactivation of that key.
If an account has been deleted intentionally and consuming implementations are expected to recognize this, regardless of whether or not a movedTo value has been set, a server MUST include the string \"Tombstone\" in the type array of the deactivated or moved Actor object. Whether any other types are present is out of scope of this specification, to minimize side effects or complications for implementers.
If an account whose Actor object containing a valid copiedTo value has been deleted intentionally, this MUST be removed before adding Tombstone to the Actor's type. A server performing this removal MAY move one valid URI from copiedTo to movedTo to aid discovery.
If a user account is being deactivated but the source server wants to enable a future migration to be authenticated cryptographically, it MAY add to the Actor object a public key authenticated to the account (if not already present), as per to FEP-521a.
An Actor object set to Tombstone SHOULD also set a top-level as:deleted property containing a current XSD dateTime as a courtesy to consumers.
After these changes have been made to the Actor object on the source server, it should be announced according to the type of changes made. If movedTo or copiedTo have been changed, then a FEP-73cd-compatible Move activity with the Actor as its object SHOULD be sent out, at least to all of that Actor's Followers. If the Actor is being retyped to Tombstone, an Announce activity with the Actor as object should be sent out, at least to all of that Actor's followers.
If a user account is being deactivated but the source server wants to enable a future migration to be authenticated cryptographically, it is RECOMMENDED that the Announce activity be signed as per FEP-8b32. In this way, a later Move activity signed by the same key could, at least theoretically, be accepted from a new server which authenticates it by FEP-8b32.
"},{"location":"fep/e965/#interpreting-a-migrated-or-deactivated-actor-object","title":"Interpreting a Migrated or Deactivated Actor Object","text":"In the section, \"movedTo and copiedTo properties\" of FEP-7628, the following general rule for all Actor objects is proposed:
Publishers SHOULD NOT deliver activities to actor's inbox if movedTo property is present.
We add the following behavioral expectations:
movedTo property to find out if it contains an inbox property.movedTo value is set and one or more copiedTo values are set, publishers MAY resolve a copiedTo value to retrieve an inbox value and similarly process it.copiedTo inbox and allowance by policy, delivery MAY attempt delivery to both Actor inbox and copiedTo inbox(es).movedTo value has been set to a valid URI, but type does not include \"Tombstone\", consuming implementations SHOULD treat it as a deactivated actor per Postel's Law.There are caveats to interpreting these values if the movedTo or copiedTo properties contain unconventional URLs, such as those generated by an implementation extended by the above-mentioned FEPs:
movedTo or copiedTo value is a valid URL beginning with the prefix ap:// and the @context value includes the relevant extension properties, the destination server of the migration is likely implementing FEP-ef61 and may require custom resolution logic to return an Actor object.movedTo or copiedTo value contains an actor-relative URL of the type defined in FEP-7952, it should resolve as usual if the server is live, as long as the querying implementation allows for the HTTP redirect and has no policy against (or hardcoded assumptions incompatible with) inbox values on different domains than id values for a given Actor.movedTo or a non-empty copiedTo value in turn, this should in turn be dereferenced, barring domain-based policies to the contrary.movedTo values be displayed to end-users as corrupted or incomplete moves, rather than as deactivated accounts.Servers receiving a Move or an Announce activity with an Actor as its object SHOULD NOT increment a shares collection. If a receiving server persists redirects or aliases to more smoothly remain aware of migrating or multi-homed users, or for other reasons, it MAY resolve the new Actor object and perform the above-described checks and MAY record said Actor update.
FEP-ef61: Portable Objects
Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/e965/fep-e965-test-case/","title":"Actor Object Migration and Deactivation Syntax","text":""},{"location":"fep/e965/fep-e965-test-case/#background","title":"Background","text":"This proposal extends and combines prior FEPs to define syntax and parsing rules for Actor objects which unambiguously express exactly one of the three following states:
This is a Test Case testing conformance with the two properties of an Actor object specified in FEP-e965.
"},{"location":"fep/e965/fep-e965-test-case/#test-subject","title":"Test Subject","text":"The subject of this test is any data claiming to conform to the specification of an ActivityPub Actor Object and to the FEP-e965 extension thereof.
This test is not directly applicable to an ActivityPub Server. An ActivityPub Server serves 0 or more Actor Objects. An ActivityPub Server for a big community might serve hundreds of ActivityPub Actor Objects. An ActivityPub Server for a single human may serve only that person's ActivityPub Actor Object.
This test applies to Actor Objects, but not all Actor Objects are addressable by an HTTPS URL. The URI that addresses an Actor Object is not the same as the Actor Object. A given URL may resolve to different Actor Objects in different contexts, and a given Actor Object may not be universally addressable across context by any one URL.
"},{"location":"fep/e965/fep-e965-test-case/#inputs","title":"Inputs","text":"This test requires the following inputs:
actor - the actor object under testedinapplicable.@context array should include both terms defined by FEP-7628 to signal support for this FEPinapplicable.This test applies directly to the actor input.
actor is not a JSON object, the outcome MUST be inapplicable.actor MUST have a @context property whose value is an Array containing the string https://w3id.org/fep/7628. If it does not, the outcome MUST be inapplicable.actor is the only test targetmovedTo - MUST be a URI OR an empty string, if presentcopiedTo - MUST be a URI, if presentmovedTo and copiedTo MUST NOT both be presentactor JSON's @context array SHOULD include \"https://w3id.org/fep/7628\" to signal conformanceFor the purposes of determining whether the active status and migration history of a given Actor can be tested by this test case:
@context property@context array includes the URL \"https://w3id.org/fep/7628\"A warning should be returned if this value is not present.
"},{"location":"fep/e965/fep-e965-test-case/#2-property-value-expectations","title":"2. Property value expectations","text":"For the purposes of determining the in/active status and migration history of a the target Actor:
movedTo and copiedTo MUST NOT both be presentmovedTo can be a valid URI OR not presentcopiedTo can be a valid URI or an array containing one or more valid URIs OR not presentmovedTo and copiedTo MUST NOT both be presentFAILEDmovedTo is present,PASSEDFAILEDcopiedTo is present,type MUST not include \"Tombstone\"FAILED; log (\"Cannot be tombstoned if copiedTo is set\")PASSEDFAILED; log (\"invalid values in copiedTo\")PASSEDThese are test cases for this test case, and can be used to verify that an implementation of this test case specification will be consistent with other implementations.
"},{"location":"fep/e965/fep-e965-test-case/#missing-context-values","title":"Missing@context values","text":"input
actor:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\"\n}\n test result
inapplicablemovedTo and copiedTo present","text":"input
actor:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"movedTo\": \"https://otherexample.com/newname\",\n \"copiedTo\": \"https://otherexample.com/thirdname\"\n}\n test return
FAILED, log (movedTo and copiedTo MUST NOT both be present)movedTo set to array","text":"input
actor:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"movedTo\": [\n \"https://example2.com/id\",\n \"https://example3.com/id\"\n ],\n}\n test return
FAILED, log (movedTo MUST be a functional property)copiedTo contains invalid URI","text":"input
actor:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"copiedTo\": [\n \"https://example2.com/id\",\n \"Tombstone\"\n ],\n}\n test return
FAILED, log (movedTo MUST be a functional property)movedTo set to invalid URI #1","text":"input
actor:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"movedTo\": \"Tombstone\"\n}\n test return
FAILED, log (movedTo MUST be a URI)movedTo set to invalid URI #2","text":"input
actor:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"movedTo\": \"\"\n}\n test return
FAILED, log (movedTo MUST be a URI)input
actor:{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": [\"Person\",\"Tombstone\"],\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\"\n}\n test return
PASSEDinput
actor:{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": [\"Person\",\"Tombstone\"],\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"movedTo\": \"https://actorname.otherexample.com\"\n}\n test return
PASSEDThis test vector does NOT conform to [FEP0-f2a] but DOES conform to the older FEP-7628. It is included to assist in testing the consumption of legacy migrated actors.
input
actor:{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": [\"Person\"],\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"movedTo\": \"https://actorname.otherexample.com\"\n}\n test return
FAILEDinput
actor:{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"copiedTo\": \"https://personalarchive.otherexample.com\"\n}\n test return
PASSEDcopiedTo set to array)","text":"input
actor:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"copiedTo\": [\n \"https://example2.com/id\",\n \"https://example3.com/id\"\n ],\n}\n test return
PASSED, log (copiedTo contains multiple valid URIs)outcome","text":"An outcome is a conclusion that comes from evaluating a test on a test subject. An outcome can be one of the three following types:
inapplicable: No part of the test subject matches the applicabilitypassed: A test target meets all expectationsfailed: A test target does not meet all expectationsassertionMethod has outcome passed, requirement is satisfiedassertionMethod has outcome failed, requirement is not satisfiedassertionMethod has outcome inapplicable, further testing is needed to determine whether this requirement is satisfiedDRAFT 2024-07-25 #386 Discussions codeberg"},{"location":"fep/eb22/#summary","title":"Summary","text":"Servers can advertise what features of the API they support, such as creating a poll or boosting a post. Clients can recognize if a server doesn't support a feature and hide it from the UI.
"},{"location":"fep/eb22/#background","title":"Background","text":"As the fediverse grows, we'll naturally see a wide range of servers and clients, and not all of them will exactly match the features that popular clients like Mastodon support. To support a diverse mix of clients with different user experiences, clients and servers should communicate about which features should be shown in the client UI.
Some services might not include public likes or polls, for example. Some might not include boosting. It would be confusing for a boost icon to show an error message when clicked if that feature was not available. Instead, the client should detect whether boosting is available, and if not simply hide the icon when connected to that server.
"},{"location":"fep/eb22/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this specification are to be interpreted as described in RFC 2119.
"},{"location":"fep/eb22/#proposal","title":"Proposal","text":"Document in NodeInfo the ActivityStreams types and properties that correspond to features the server supports. This information can be used by both ActivityPub clients as well as Mastodon API clients. We use ActivityStreams types because they are already part of a fediverse standard, rather than inventing new keys to describe existing features.
Add a new top-level field types to NodeInfo with three fields, all of which are optional:
activities: The types of activities the server broadly supports.objects: The types of objects that the server supports in the relevant activities.properties: For each activity or object, what properties are allowed in that object.If no types are present in NodeInfo, a client should assume a server supports all types, just as most clients do today.
If no objects are present for an activity, a client should assume a server supports all common objects. If a limited number of objects are present, a client should assume a server only supports those objects.
If no properties are present for an object, a client should assume a server supports all common properties. If a limited number of properties are present, a client should assume a server only supports those properties.
"},{"location":"fep/eb22/#examples","title":"Examples","text":"The following examples are simplified versions of what most servers should support. Most popular servers will support more activity and object types than this, but these examples keep the list short for readability. Other common activities include Follow, Undo, Accept, Block, and others as listed in the Activity Vocabulary specification.
Review)","text":"This example adds Review, an object used by BookWyrm for book reviews. Because only properties for Review are specified, a client can assume that all common objects and properties for the other activities are supported.
{\n \"types\": {\n \"activities\": [\n \"Create\",\n \"Like\",\n \"Announce\",\n \"Question\",\n \"Move\",\n \"Follow\"\n ],\n \"objects\": [\n \"Note\",\n \"Article\",\n \"Image\",\n \"Review\"\n ],\n \"properties\": {\n \"Review\": [ \"title\", \"body\", \"rating\", \"inReplyToBook\" ]\n }\n }\n}\n"},{"location":"fep/eb22/#supporting-common-social-features-but-not-supporting-polls-question","title":"Supporting common social features, but not supporting polls (Question)","text":"This example leaves off the Question type and the oneOf property used for polls.
{\n \"types\": {\n \"activities\": [\n \"Create\",\n \"Like\",\n \"Announce\"\n ],\n \"objects\": [\n \"Note\",\n \"Article\",\n \"Image\"\n ],\n \"properties\": {\n \"Note\": [ \"summary\", \"content\", \"published\", \"inReplyTo\" ],\n \"Article\": [ \"name\", \"content\", \"published\" ]\n }\n }\n}\n"},{"location":"fep/eb22/#supporting-common-social-features-including-polls-but-not-supporting-titled-posts-name","title":"Supporting common social features including polls, but not supporting titled posts (name)","text":"This example includes only the content and published properties for Article, meaning the client should not prompt for a title for long-form articles.
{\n \"types\": {\n \"activities\": [\n \"Create\",\n \"Like\",\n \"Announce\",\n \"Question\",\n ],\n \"objects\": [\n \"Note\",\n \"Article\",\n \"Image\"\n ],\n \"properties\": {\n \"Question\": [ \"oneOf\" ],\n \"Note\": [ \"summary\", \"content\", \"published\", \"inReplyTo\" ],\n \"Article\": [ \"content\", \"published\" ]\n }\n }\n}\n"},{"location":"fep/eb22/#supporting-common-social-features-but-not-supporting-boosts-announce","title":"Supporting common social features, but not supporting boosts (Announce)","text":"This example lists the bare minimum types, leaving off the explicit list of objects and properties. Clients should assume the server supports whatever properties are common for those types, such as Note, Article, and others.
{\n \"types\": {\n \"activities\": [\n \"Create\",\n \"Like\",\n \"Question\",\n \"Move\"\n ]\n }\n}\n"},{"location":"fep/eb22/#supporting-common-social-features-and-account-migration-move","title":"Supporting common social features and account migration (Move)","text":"This example lists object and target fields that are used for account migration with the Move activity.
{\n \"types\": {\n \"activities\": [\n \"Create\",\n \"Like\",\n \"Announce\",\n \"Question\",\n \"Move\"\n ],\n \"properties\": {\n \"Move\": [ \"object\", \"target\" ]\n }\n }\n}\n"},{"location":"fep/eb22/#related-proposals","title":"Related proposals","text":"There have been a few other attempts at addressing how to describe what a server supports:
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/eb48/","title":"FEP-eb48: Hashtags","text":"Authors Status Date received Tracking issue Discussions Repository AvidSeeker avidseeker7@protonmail.comDRAFT 2024-07-16 #373 Discussions codeberg"},{"location":"fep/eb48/#summary","title":"Summary","text":"This proposal introduces a standardized method for identifying and displaying hashtags in posts across the Fediverse. The rules define what constitutes a hashtag and how it should be parsed and displayed, ensuring consistency and predictability across different platforms and clients.
"},{"location":"fep/eb48/#hashtag-rules","title":"Hashtag Rules","text":"A string is considered a hashtag if it meets the following criteria:
# symbol._) but must not include any other special characters, spaces, or punctuation within the hashtag itself.The following examples illustrate how hashtags should be identified and highlighted:
#hashtag#hashtag\"#hashtag\"#hashtag/#hashtag)#hashtag/#hashtag)#hashtag /#hashtag)#hashtag / #hashtag)#hashtag#hashtag#hashtag#hashtag#hashtag#hashtag#hashtag#hashtag#hashtag#hashtag#hashtagCC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/ee3a/","title":"FEP-ee3a: Exif metadata support","text":"Authors Status Date received Discussions Repository Marcin Czachurski mczachurski@icloud.comDRAFT 2026-01-13 Discussions codeberg"},{"location":"fep/ee3a/#summary","title":"Summary","text":"The exchangeable image file format ([Exif]) family combines file formats such as JPEG, TIFF and WAV with structured metadata. [Exif] records camera (e.g., lens data, focal length, exposure time) and audio (e.g., channel count, sampling rate) recording parameters. The standard originally focused on photography but was expanded with version 2.1 to cover sound recordings. This proposal defines a Fediverse-wide mechanism for conveying [Exif] metadata using the [exifData] property from the [Schema.org] vocabulary.
To improve interoperability and promote consistent handling of attachment metadata across [ActivityPub] implementations, this FEP defines a vocabulary and processing rules for exposing Exif metadata associated with image (Image) and audio (Audio) attachments.
"},{"location":"fep/ee3a/#description","title":"Description","text":"In this FEP, Exif metadata is represented as an array of [PropertyValue] items under the exifData property of an Image or Audio object. Each item MUST include an @type of PropertyValue, a name identifying the Exif tag and a value holding the tag's value. All Exif tags defined in the official Exif specification may be represented, but implementations are encouraged to support a recommended subset for interoperability.
Exif metadata can reveal sensitive information such as a user's location, device details or recording environment. Geographic coordinates (latitude and longitude) can expose personal addresses or travel patterns. Producers MUST obtain user consent before including these fields and SHOULD clearly inform users about what information will be published. Consumers SHOULD treat geographic and device metadata as sensitive and avoid displaying it publicly without explicit permission.
"},{"location":"fep/ee3a/#definitions","title":"Definitions","text":""},{"location":"fep/ee3a/#exifdata-property","title":"exifData property","text":"exifData is an optional property attached to media objects of type Image or Audio. When present, its value MUST be an array of objects where each object: - has @type equal to \"PropertyValue\"; - has a name property containing the Exif field name (e.g., \"ExposureTime\", \"SamplesPerSec\"); - has a value property containing the corresponding value.
Implementations MUST ignore unknown properties. Schema.org's definition of exifData permits the value to be a string, but this FEP standardizes on the structured array representation for interoperability.
Property names MUST be strings corresponding to the Exif specification Field name (e.g., \"FNumber\", \"PhotographicSensitivity\", \"FocalLength\").
Implementations MAY expose any Exif tag via exifData when present in the file and permitted by user consent. For interoperability, the following tag names and descriptions are recommended. Fields that apply only to images or only to audio are indicated in the second column.
DateTime image, audio Date and time when the media was created. Exif's DateTime tag uses the format \"YYYY:MM:DD HH:MM:SS\". The time is expressed in the photographer's local time zone. ExposureTime image Exposure time (e.g., \"1/100\" or \"4\"). FNumber image Aperture value expressed as an f-number (e.g., \"f/1.8\"). Flash image Description of flash usage (e.g., \"Flash did not fire.\"). FocalLength image Focal length reported by the camera. FocalLengthIn35mmFilm image 35 mm equivalent focal length. GPSLatitude image Exact latitude of the photo location. GPSLatitudeRef image Indicates whether the latitude of shooting location is north or south latitude. 'N' indicates north latitude, and 'S' is south latitude. GPSLongitude image Exact longitude of the photo location (requires user consent). GPSLongitudeRef image Indicates whether the longitude of shooting location is east or west longitude. 'E' indicates east longitude, and 'W' is west longitude. LensMake image Lens manufacturer. LensModel image Lens model name. Make image, audio Device manufacturer. Model image, audio Device model. PhotographicSensitivity image ISO sensitivity. Software image, audio Editing software or firmware used. SamplesPerSec audio Sampling frequency (e.g., \"44100 Hz\"). AvgBytesPerSec audio Bit depth per sample (e.g., \"16 bit\"). Channels audio Channel configuration (e.g., \"mono\", \"stereo\"). Compression audio Compression scheme (e.g., \"PCM\", \"\u03bc-Law\", \"ADPCM\"). Implementations MAY include additional tags and MUST ignore tags they do not understand. Property names SHOULD be consistent across implementations to facilitate display and filtering.
"},{"location":"fep/ee3a/#examples","title":"Examples","text":""},{"location":"fep/ee3a/#image-example","title":"Image example","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"schema\": \"https://schema.org/\"\n }\n ],\n \"type\": \"Note\",\n \"content\": \"Sunrise photo.\",\n \"attachment\": [{\n \"type\": \"Image\",\n \"url\": \"https://example.org/photos/123.jpg\",\n \"mediaType\": \"image/jpeg\",\n \"exifData\": [\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"DateTime\",\n \"value\": \"2025:03:30 06:30:00\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"ExposureTime\",\n \"value\": \"1/250\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"FNumber\",\n \"value\": \"f/5.6\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"FocalLength\",\n \"value\": \"70 mm\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"LensModel\",\n \"value\": \"Canon EF 70-200mm\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Make\",\n \"value\": \"Canon\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Model\",\n \"value\": \"EOS R5\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"PhotographicSensitivity\",\n \"value\": \"400\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Software\",\n \"value\": \"Darktable\"\n }\n ]\n }]\n}\n"},{"location":"fep/ee3a/#audio-example","title":"Audio example","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"schema\": \"https://schema.org/\"\n }\n ],\n \"type\": \"Note\",\n \"content\": \"Field recording.\",\n \"attachment\": [{\n \"type\": \"Audio\",\n \"url\": \"https://example.org/audio/field.wav\",\n \"mediaType\": \"audio/wav\",\n \"exifData\": [\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"DateTime\",\n \"value\": \"2025-03-02T14:00:00Z\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"SamplesPerSec\",\n \"value\": \"48000 Hz\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"AvgBytesPerSec\",\n \"value\": \"24 bit\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Channels\",\n \"value\": \"stereo\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Compression\",\n \"value\": \"PCM\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Make\",\n \"value\": \"Sony\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Model\",\n \"value\": \"PCM-D10\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Software\",\n \"value\": \"Audacity\"\n }\n ]\n }]\n}\n"},{"location":"fep/ee3a/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/ef61/","title":"FEP-ef61: Portable Objects","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2023-12-06 #209 Discussions codeberg"},{"location":"fep/ef61/#summary","title":"Summary","text":"Portable ActivityPub objects with server-independent IDs.
"},{"location":"fep/ef61/#motivation","title":"Motivation","text":"Usage of HTTP(S) URIs as identifiers has a major drawback: when the server disappears, everyone who uses it loses their identity and data.
The proposed solution should satisfy the following constraints:
Nomadic identity mechanism makes identity independent from a server and was originally part of the Zot federation protocol.
Streams (2021) made nomadic accounts available via the Nomad protocol, which supported ActivityStreams serialisation.
FEP-c390 (2022) introduced a decentralized identity solution compatible with ActivityPub. It enabled permissionless migration of followers between servers, but didn't provide full data portability.
"},{"location":"fep/ef61/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/ef61/#identifiers","title":"Identifiers","text":"An ActivityPub object can be made portable by using an identifier that is not tied to a single server. This proposal describes a new identifier type that has this property and is compatible with the ActivityPub specification.
"},{"location":"fep/ef61/#ap-uris","title":"'ap' URIs","text":"'ap' URI is constructed according to the RFC-3986 specification, but with a Decentralized Identifier in place of the authority:
ap://did:example:abcdef/path/to/object?name=value#fragment-id\n\\_/ \\________________/ \\____________/ \\________/ \\_________/\n | | | | |\nscheme authority path query fragment\n ap.Warning
An 'ap' URI is not a valid RFC-3986 URI if reserved characters in the authority component are not percent-encoded. Nevertheless, this form is considered canonical.
Note
ActivityPub specification requires identifiers to have an authority \"belonging to that of their originating server\". The authority of 'ap' URI is a DID, which does not belong to any particular server.
Warning
The URI scheme might be changed to ap+ef61 in a future version of this document, because these identifiers are not intended to be used for all ActivityPub objects, but only for portable ones.
Two 'ap' URIs are equivalent when their canonical forms are identical.
To produce a canonical 'ap' URI, the following operations MUST be performed:
Implementers MUST support the did:key method. Other DID methods SHOULD NOT be used, as it might hinder interoperability.
Note
The following additional DID methods are being considered: did:web, did:dns, did:webvh (formerly did:tdw) and did:fedi.
To maintain backward compatibility with existing ActivityPub implementations that rely on an origin-based security model and do not canonicalize IDs before comparison, implementers MUST generate DIDs using the base58-btc alphabet, even though the specification allows both base58-btc and base64url. Using both alphabets in practice could prevent such servers from recognizing that a post whose attributedTo value is https://base64url.example/.well-known/apgateway/did:key:u7QGwDY2Tjn93PVFWWq02piP1NE9_XRlg-c8-jhJiDqKBDw/actor belongs to https://base58.example/.well-known/apgateway/did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/actor.
DID documents SHOULD contain Ed25519 public keys represented as verification methods with Multikey type (as defined in the Controlled Identifiers specification).
Any DID URL capabilities of a DID method MUST be ignored when working with 'ap' URIs.
"},{"location":"fep/ef61/#dereferencing-ap-uris","title":"Dereferencing 'ap' URIs","text":"To dereference an 'ap' URI, the client MUST make HTTP GET request to a gateway endpoint at well-known location /.well-known/apgateway. The ap:// prefix MUST be removed from the URI and the rest of it appended to a gateway URI. The client MUST specify an Accept header with the application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\" media type.
Example of a request to a gateway:
GET https://social.example/.well-known/apgateway/did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/path/to/object\n ActivityPub objects identified by 'ap' URIs can be stored on multiple servers simultaneously.
If object identified by 'ap' URI is stored on the server, it MUST return a response with status 200 OK containing the requested object. The value of a Content-Type header MUST be application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\".
If object identified by 'ap' URI is not stored on the server, it MUST return 404 Not Found.
If object is not public, the server MUST return 404 Not Found unless the request has a HTTP signature and the signer is allowed to view the object.
Note
This document describes web gateways, which use HTTP transport. However, the data model and authentication mechanism are transport-agnostic and other types of gateways could exist.
"},{"location":"fep/ef61/#authentication-and-authorization","title":"Authentication and authorization","text":"Authentication and authorization are performed in accordance with FEP-fe34 origin-based security model, but with two important differences:
The origin of an 'ap' URI is identical to the authority component of its canonical form (i.e. it is a DID without percent encoding).
The origin of a DID URL is identical to its did component.
Actors, activities and objects identified by 'ap' URIs MUST contain FEP-8b32 integrity proofs. Collections identified by 'ap' URIs MAY contain integrity proofs. If collection doesn't contain an integrity proof, another authentication method MUST be used.
The value of verificationMethod property of the proof MUST be a DID URL where the DID matches the authority component of the 'ap' URI.
Note
This document uses terms \"actor\", \"activity\", \"collection\" and \"object\" according to the classification given in FEP-2277.
"},{"location":"fep/ef61/#portable-actors","title":"Portable actors","text":"One DID subject can control multiple actors (which are differentiated by the path component of an 'ap' URI).
An actor object identified by 'ap' URI MUST have a gateways property containing an ordered list of gateways where the latest version of that actor object can be retrieved. Each item in the list MUST be an HTTP(S) URI with empty path, query and fragment components. The list MUST contain at least one item.
Gateways are expected to be the same for all actors under a DID authority and MAY be also specified in the DID document as services.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v1\",\n \"https://w3id.org/fep/ef61\"\n ],\n \"type\": \"Person\",\n \"id\": \"ap://did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/actor\",\n \"inbox\": \"ap://did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/actor/inbox\",\n \"outbox\": \"ap://did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/actor/outbox\",\n \"gateways\": [\n \"https://server1.example\",\n \"https://server2.example\"\n ],\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"created\": \"2023-02-24T23:36:38Z\",\n \"verificationMethod\": \"did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2#z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\"\n }\n}\n"},{"location":"fep/ef61/#location-hints","title":"Location hints","text":"When ActivityPub object containing a reference to another actor is being constructed, implementations SHOULD provide a list of gateways where specified actor object can be retrieved. This list MAY be provided using the gateways query parameter. Each gateway address MUST be URI-endcoded, and if multiple addresses are present they MUST be separated by commas.
Example:
ap://did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/actor?gateways=https%3A%2F%2Fserver1.example,https%3A%2F%2Fserver2.example\n This URI indicates that object can be retrieved from two gateways:
https://server1.examplehttps://server2.exampleImportant
When comparing 'ap' URIs, query parameters are discarded and canonical URIs are used.
"},{"location":"fep/ef61/#inboxes-and-outboxes","title":"Inboxes and outboxes","text":"Portable inboxes and outboxes function as described in the ActivityPub specification. These endpoints are also used to synchronize activities between gateways used by an actor.
Servers specified in the gateways property of an actor object MUST accept POST requests targeting its inbox collection.
Example:
POST https://social.example/.well-known/apgateway/did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/actor/inbox\n Activities delivered to an inbox might be not portable. If the server does not accept deliveries on behalf of an actor, it MUST return 404 Not Found.
Upon receiving an activity in actor's inbox, the server SHOULD forward it to inboxes located on other servers where actor's data is stored. An activity MUST NOT be forwarded from inbox more than once.
Servers specified in the gateways property of an actor object MAY accept POST requests targeting its outbox collection. Such servers MUST implement FEP-ae97.
Activities delivered to an outbox are performed by a portable actor and therefore MUST be portable too. The server MUST verify them as described in section Authentication and authorization and then process them as described in FEP-ae97. Clients MAY deliver activities to multiple outboxes, located on different servers.
Upon receiving an activity in actor's outbox, the server SHOULD forward it to outboxes located on other servers where actor's data is stored. An activity MUST NOT be forwarded from outbox more than once.
"},{"location":"fep/ef61/#collections","title":"Collections","text":"Collections associated with portable actors (such as inbox and outbox collections) MAY not have FEP-8b32 integrity proofs. Consuming implementations MUST NOT process unsecured collections retrieved from servers that are not listed in the gateways array of the actor document.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v1\",\n \"https://w3id.org/fep/ef61\"\n ],\n \"type\": \"Note\",\n \"id\": \"ap://did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/objects/dc505858-08ec-4a80-81dd-e6670fd8c55f\",\n \"attributedTo\": \"ap://did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/actor?gateways=https%3A%2F%2Fserver1.example,https%3A%2F%2Fserver2.example\",\n \"inReplyTo\": \"ap://did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/objects/f66a006b-fe66-4ca6-9a4c-b292e33712ec\",\n \"content\": \"Hello!\",\n \"attachment\": [\n {\n \"type\": \"Image\",\n \"url\": \"hl:zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n\",\n \"mediaType\": \"image/png\",\n \"digestMultibase\": \"zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n\"\n }\n ],\n \"to\": [\n \"ap://did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/actor\"\n ],\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"created\": \"2023-02-24T23:36:38Z\",\n \"verificationMethod\": \"did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2#z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\"\n }\n}\n"},{"location":"fep/ef61/#media","title":"Media","text":"Integrity of an external resource is attested with a digest. When a portable object contains a reference to an external resource (such as image), it MUST also contain a digestMultibase property representing the integrity digest of that resource. The digest MUST be computed using the SHA-256 algorithm.
The URI of an external resource SHOULD be a hashlink.
Example of an Image attachment:
{\n \"type\": \"Image\",\n \"url\": \"hl:zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n\",\n \"mediaType\": \"image/png\",\n \"digestMultibase\": \"zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n\"\n}\n After retrieving a resource, the client MUST verify its integrity by computing its digest and comparing the result with the value encoded in digestMultibase property.
Resources attached to portable objects using hashlinks can be stored by gateways. To retrieve a resource from a gateway, the client MUST make an HTTP GET request to the gateway endpoint at well-known location /.well-known/apgateway. The value of a hashlink URI MUST be appended to the gateway base URI.
Example of a request:
GET https://social.example/.well-known/apgateway/hl:zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n\n"},{"location":"fep/ef61/#compatibility","title":"Compatibility","text":""},{"location":"fep/ef61/#identifiers_1","title":"Identifiers","text":"'ap' URIs might not be compatible with existing ActivityPub implementations. To provide backward compatibility, gateway-based HTTP(S) URIs of objects can be used instead of their canonical identifiers:
https://social.example/.well-known/apgateway/did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/path/to/object\n Publishers MUST use the first gateway from actor's gateways list when constructing compatible identifiers. Consuming implementations that support 'ap' URIs MUST remove the part of the URI preceding did: and re-construct the canonical identifier. Objects with the same canonical identifier, but located on different gateways MUST be treated as different instances of the same object.
Publishers MUST NOT add the gateways query parameter to object IDs if compatible identifiers are used.
When HTTP signatures are necessary for communicating with other servers, each gateway that makes requests on behalf of an actor SHOULD use a separate secret key. The corresponding public keys MUST be added to actor document using the assertionMethod property as described in FEP-521a.
WebFinger address of a portable actor can be obtained by the reverse discovery algorithm described in section 2.2 of ActivityPub and WebFinger report, but instead of taking the hostname from the identifier, it MUST be taken from the first gateway in actor's gateways array.
(This section is non-normative.)
"},{"location":"fep/ef61/#discovering-locations","title":"Discovering locations","text":""},{"location":"fep/ef61/#arbitrary-paths","title":"Arbitrary paths","text":"The gateways array can contain HTTP(S) URIs with a path component, thus enabling discovery based on the \"follow your nose\" principle, as opposed to discovery based on a well-known location.
Example of a compatible object ID if the gateway endpoint is https://social.example/ap:
https://social.example/ap/did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/path/to/object\n"},{"location":"fep/ef61/#alternatives-to-gateways-property","title":"Alternatives to gateways property","text":"This proposal makes use of the gateways property, but the following alternatives are being considered:
gateways property in actor's endpoints mappingaliases and sameAs (containing HTTP(S) URIs of objects)alsoKnownAs (used for account migrations, so the usage of this property may cause issues)url (with alternate relation type)Instead of specifying gateways in actor document, they can be specified in DID document using DID services. This approach is not compatible with generative DID methods such as did:key, which might be necessary for some types of applications.
The proposed approach to referencing media with hashlinks does not support access control: anybody who knows the hash can retrieve the file.
To work around this limitation, a different kind of identifier can be used where digest is combined with the ap:// identifier of its parent document. The gateway will not serve media unless parent document ID is provided, and will check whether request signer has permission to view the document and therefore the attached media.
The following alternatives to gateway-based compatible IDs are being considered:
url property (with canonical relation type, as proposed in FEP-fffd). For pointers to other objects such as inReplyTo property, an embedded object with url property can be used instead of a plain URI.CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/efda/","title":"FEP-efda: Followable objects","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT 2025-02-13 #492 Discussions codeberg"},{"location":"fep/efda/#summary","title":"Summary","text":"ActivityStreams Vocabulary defines a Follow activity, and ActivityPub defines its side effects of manipulating a followers collection, but ActivityPub does not specify a full algorithm for how to follow something. This FEP aims to provide guidance on which objects can be followed:
followers collection present.inbox, then you MAY recurse upwards through attributedTo until you find a resource with an inbox. The maximum recursion depth SHOULD be 1.A Follow activity can then be constructed for that object and delivered to the discovered inbox. Additional requirements for the structure of the Follow activity are out-of-scope.
In order to follow an object, we use a Follow activity. But in order to use this Follow activity, we need to know the following:
1) What can be followed? 2) Where do we send the Follow for that object?
At the time of writing, current software practices within the fediverse enforce a limit on what can be followed. For example, Mastodon currently limits follows to actors that can be mapped to their internal concept of \"accounts\", and these \"accounts\" are in turn limited to being any of the five \"actor types\" described in AS2-Vocab: Person, Group, Organization, Application, Service.
Consequently, other software which does not have such a conceptual limit is forced to declare their actors as one of the five types, or else Mastodon users will not be able to follow their actors. ActivityPub says that there is no specified mapping between \"users\" and \"actors\", but Mastodon's interpretation of \"actors\" as \"users\" de facto disallows following anything but a strict subset of resources.
We may therefore express Mastodon's requirements as follows:
type of any of Person, Group, Organization, Application, Service.inbox where the Follow will be sent.A proposed modified requirement and algorithm are as follows:
followers collection present. (If it does not, then it is unknown whether it is followable.)inbox, then you MAY recurse upwards through attributedTo until you find a resource with an inbox. The maximum recursion depth SHOULD be 1.Given an object O and a recursion limit L, a general algorithm for following it can be expressed like so:
Inputs: - O (Object): an object that may be followable - L (integer): a recursion limit.
Outputs: - Error OBJECT_HAS_UNKNOWN_FOLLOWERS_COLLECTION - Error MAX_RECURSION_LIMIT - Success
(1) Initialize a variable INBOX. (2) If O.followers is not present, optionally return an error OBJECT_HAS_UNKNOWN_FOLLOWERS_COLLECTION. (3) If O.inbox is present, set INBOX to the referenced IRI. (4) If INBOX is unset, then initialize a variable R whose initial value is O. (5) While INBOX is unset: (5.1) Check that L > 0. If false, return an error MAX_RECURSION_LIMIT. (5.2) Dereference R.attributedTo and set the variable R to this referenced resource. (5.3) If R.inbox is present, set INBOX to the referenced IRI. (5.4) Set the variable L to the value L - 1. (6) Initialize a document ACTIVITY: (6.1) ...whose actor is yourself (6.2) ...whose type is Follow (6.3) ...whose object is O (7) Make an HTTP POST request whose target is INBOX and whose body is ACTIVITY.
Additional requirements for the ACTIVITY can be defined at a protocol level, but the exact protocol and those requirements are out-of-scope for this FEP. The scope of this FEP is limited to determining which objects can be followed, and where to send the Follow.
"},{"location":"fep/efda/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/f06f/","title":"FEP-f06f: Object observers","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2025-02-18 #503 Discussions codeberg"},{"location":"fep/f06f/#summary","title":"Summary","text":"Object observer is an ActivityPub actor that can be followed to receive object updates.
This proposal is intended to complement FEP-bad1: Object history collection.
"},{"location":"fep/f06f/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/f06f/#observers","title":"Observers","text":"Object observer is a followable actor. It doesn't perform any activities on its own, but activities that affect the observed object are forwarded to its followers. Only objects and collections can be observed.
Object observer MUST have an observerOf property specifying the observed object, and it SHOULD have an Application type.
Objects can specify their observers using the observer property.
Object observer can be created with ActivityPub client by publishing a Create activity with user's actor as its actor and with observer actor as its object.
Implementers MAY use a single cryptographic key for signing all HTTP requests made by observers on a server.
"},{"location":"fep/f06f/#use-case-subscribing-to-a-conversation","title":"Use case: subscribing to a conversation","text":"When conversation is represented by a collection, a collection observer can be created to support conversation subscriptions.
This actor can be attached to a collection via observer property, and can forward Add and Remove activities that modify it.
If forwarding is not desirable, object observers can use Announce activity to distribute observed activities.
Example of an observer actor:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Application\",\n \"id\": \"https://server.example/objects/123456/observer\",\n \"inbox\": \"https://server.example/objects/123456/observer/inbox\",\n \"outbox\": \"https://server.example/objects/123456/observer/outbox\",\n \"observerOf\": \"https://server.example/objects/123456\"\n}\n"},{"location":"fep/f06f/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/f15d/","title":"FEP-f15d: Context Relocation and Removal","text":"Authors Status Date received Tracking issue Discussions Repository Julian Lam julian@nodebb.org, Felix Ableitner me@nutomic.com, Rimu Atkinson rimu@rimu.geek.nzDRAFT 2026-01-12 #751 Discussions codeberg"},{"location":"fep/f15d/#summary","title":"Summary","text":"Threaded applications often have the need to move and remove content between groups/communities for curation purposes (i.e. resolving miscategorization, spam, etc.)
This is an extension of the Resolvable Contexts tree of FEPs.
"},{"location":"fep/f15d/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in [RFC-2119].
"},{"location":"fep/f15d/#assumptions","title":"Assumptions","text":""},{"location":"fep/f15d/#nomenclature","title":"Nomenclature","text":"The Forums and Threaded Discussions Task Force (ForumWG) has identified a common nomenclature when referring to organized objects in a threaded discussion model. The two terms used in this FEP are described below:
"},{"location":"fep/f15d/#context","title":"Context","text":"A group of related objects (i.e. via reply-association) is referred to as a \"context\". Other terms for this concept would be \"topic\" or \"thread\".
"},{"location":"fep/f15d/#audience","title":"Audience","text":"Contexts (see above) can be further grouped together into \"audiences\". Other terms for this concept would be \"community\", \"category\", or \"forum\". In ActivityPub, audiences usually take the form of a Group Actor type.
ActivityPub implementations differ in how they represent the aggregated collection of threaded objects.
Some implementations represent these collections as a distinct abstraction (e.g. a context.)
Others make no such distinction and represent them via the root-level object (e.g. link aggregators.)
The resolvable contexts tree of FEPs requires that abstraction in order to communicate actions pertaining to it in an explicit manner.
For more information, see FEP 7888.
"},{"location":"fep/f15d/#relocation","title":"Relocation","text":"When a publisher relocates a context from one audience to another, a Move activity is published to both audiences' followers.
{\n @context: \"https://www.w3.org/ns/activitystreams\",\n id: \"https://example.social/context/123#activity/move/<timestamp>\",\n type: \"Move\",\n actor: \"https://example.social/uid/1\",\n to: [\"https://www.w3.org/ns/activitystreams#Public\"],\n cc: [\n \"https://example.social/audience/1/followers\",\n \"https://example.social/audience/2/followers\",\n ],\n object: \"https://example.social/context/1\",\n origin: \"https://example.social/audience/1\",\n target: \"https://example.social/audience/2\",\n}\n Note the following properties:
actor is the user actor carrying out the action; typically a moderator (see Security Considerations)cc contains the follower collections of both the outgoing and incoming audiencesobject is the resolvable contextorigin is the outgoing audiencetarget is the incoming audienceAudience wrapper","text":"Implementations MAY wrap the Move activity in an Announce activity. Receivers are recommended to support both types.
There are two ways to signal that a context is removed from an audience.
Note
A removal is functionally different from a deletion. You cannot delete content that is originally from another server, as you are not the owner, but you are able to remove it from yours.
"},{"location":"fep/f15d/#resolvable-context-variant","title":"Resolvable context variant","text":"When a publisher removes a context from an audience, a Remove activity is published to the audience's followers.
{\n @context: \"https://www.w3.org/ns/activitystreams\",\n id: \"https://example.social/context/123#activity/remove/<timestamp>\",\n type: \"Remove\",\n actor: \"https://example.social/uid/1\",\n to: [\"https://www.w3.org/ns/activitystreams#Public\"],\n cc: [\"https://example.social/audience/1/followers\"],\n object: \"https://example.social/context/1\",\n target: \"https://example.social/audience/1\",\n}\n"},{"location":"fep/f15d/#group-federation-variant","title":"Group federation variant","text":"This sub-section is non-normative
Implementations may also handle the federated removal of objects by federating out a Delete referencing the root-level object. This activity is further wrapped in an Announce activity per FEP 1b12.
{\n \"actor\": \"http://ds9.lemmy.ml/u/lemmy_alpha\",\n \"to\": [\"https://www.w3.org/ns/activitystreams#Public\"],\n \"object\": \"http://ds9.lemmy.ml/post/1\",\n \"cc\": [\"http://enterprise.lemmy.ml/c/main\"],\n \"audience\": \"http://enterprise.lemmy.ml/u/main\",\n \"type\": \"Delete\",\n \"id\": \"http://ds9.lemmy.ml/activities/delete/f2abee48-c7bb-41d5-9e27-8775ff32db12\"\n}\n Example from Join Lemmy: Section 30 \"Federation\" \u2014 \"Delete Post or Comment\"
"},{"location":"fep/f15d/#security-considerations","title":"Security Considerations","text":""},{"location":"fep/f15d/#same-origin-check","title":"Same-Origin Check","text":"As per FEP fe34, the Remove and Move events outlined in this FEP are assumed to be authentic if the actor and the audience(s) in question (target and if applicable, origin) are same-origin.
FEP-1b12: Group federation describes the use of an OrderedCollection referenced in an audience's attributedTo to represent an audience's moderators.
If the actor of the Move or Remove activities is not same-origin to the audience(s) in question, this collection SHOULD be cross-referenced for authorization.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/f1d5/","title":"FEP-f1d5: NodeInfo in Fediverse Software","text":"Authors Status Date received Date final Tracking issue Discussions Repository CJ cjslep@gmail.com, silverpill @silverpill@mitra.socialFINAL 2020-12-13 2023-06-02 #50 - codeberg"},{"location":"fep/f1d5/#summary","title":"Summary","text":"NodeInfo is a protocol intended to standardize upon a way to provide server-level metadata to the public. This enables tools and clients to utilize this metadata to assess server health or facilitate end-users choices about servers and software to use on the Fediverse.
"},{"location":"fep/f1d5/#history","title":"History","text":"NodeInfo was developed prior to the ActivityPub protocol targeted for use by diaspora, friendica, and redmatrix software. Some of the original protocols it encapsulated include diaspora, pumpio, and gnusocial.
The NodeInfo specification is incredibly strict in its schema, often requiring regex-validation and a closed set of enumerated possible values. As an objection to this, the NodeInfo2 fork was created as a form of criticism by removing some validation of fields and with some logical restructuring of the metadata. Building off of NodeInfo and NodeInfo2, ServiceInfo was briefly explored.
This FEP does not attempt to document the specific protocol details. For that, see the NodeInfo and NodeInfo2. It attempts to clarify the history and identify shortcomings with the current approaches, to bring context to developers of Fediverse Software.
"},{"location":"fep/f1d5/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this specification are to be interpreted as described in RFC-2119.
Fediverse software SHOULD implement NodeInfo.
"},{"location":"fep/f1d5/#caveats","title":"Caveats","text":"At the time of this FEP's writing, the current objections to the current state of NodeInfo that have been identified by the community are below. Note that any technical alternatives identified are meant to be illustrative and not prescriptive:
software.name regex is unnecessarily strict. For example, no uppercase letters, no spaces, no non-English-alphabet, and no special characters besides hyphen are permitted.software.version field is required, which is unnecessarily strict. Forcibly requiring software to divulge version information is potentially a security issue.inbound and outbound elements are specified as a closed set of enums instead of a simple string. Protocol versioning manifests as renaming, having to add a new enum, which results in unclear version management.openRegistrations concept due to it being required.metadata is too lax.usage.users is not denormalized, such that implementations can provide custom pairs of (activity counts, time period in days) that make sense for the software.usage.users assumes that user identity is tied to a specific instance of running software. It is unclear how to count total users when user identity is: spread across multiple servers, spread across multiple groups, or present within multiple collections of users. Multiple software instances could each have a reasonable claim to counting the user as \"using\" their software, which globally results users being counted more than once.usage.users activity counts likewise assume that user identity is tied to a specific instance of running software. For the same reasons above, where the total user counts may result in duplicate counts of the same user across all software running, the activity counts activeHalfYear and activeMonth may also result in a globally inflated count.activeHalfyear and activeMonth are ill-named properties for describing the time periods of 180 days and 30 days, respectively. A \"half of one year\" is 180 days 0% of the time and roughly 182.5 days only 75% of the time. A month is 30 days only 33% of the time.localPosts and localComments are not denormalized into pairs of (kind, counts) for software that, for example, hosts audio files, hosts videos, or software that does not have comments, or does not have posts.localPosts and localComments are required, which is problematic for software that does not have comments, or does not have posts.This list is not comprehensive:
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/f228/","title":"FEP-f228: Backfilling conversations","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2025-02-17 #500 Discussions codeberg"},{"location":"fep/f228/#summary","title":"Summary","text":"The most common conversation backfill method is based on recursive retrieval of posts indicated by inReplyTo property and posts contained in replies collections. This is inefficient and stops working if any node in the reply tree becomes inaccessible.
FEP-7888: Demystifying the context property suggests using the context property for grouping related objects (such as posts in a conversation). This property can resolve to a collection, which can be used for efficient backfilling without recursion.
Two different implementations of context collection exist: collection of posts and collection of activities.
The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/f228/#collection-of-posts","title":"Collection of posts","text":"This collection represents a thread and contains all posts in a conversation, from the perspective of the conversation owner.
It is an OrderedCollection, and the order of items is chronological. It MUST contain at least one item, the top-level post. This post MUST have a context property referring to the collection. Other posts might not have this property.
When context property is present on a post, it MUST resolve to a collection of posts.
There is a difference between contents of this collection and a reply tree defined by inReplyTo and replies relationships, because conversation owner might choose to not include certain replies. When a reply is deleted by its author, the sub-replies MAY remain in the thread collection.
Note
ActivityPub requires ordered collections to be presented in reverse chronological order. However, an erratum was proposed to relax this requirement.
"},{"location":"fep/f228/#collection-of-activities","title":"Collection of activities","text":"This collection contains all activities related to posts in a conversation, including but not limited to:
CreateUpdateDeleteLikeIt is an OrderedCollection, and the order of items is chronological. It MUST contain at least one item, the Create activity for the top-level post. This activity MUST have a context property referring to the collection. Other activities might not have this property.
When context property is present on an activity, it MUST resolve to a collection of activities.
In Conversation Containers this collection would coincide with the conversation container. It will contain Add activities in addition to other activities.
history property","text":"Collections described in this document can be implemented separately.
If both of them are implemented, the history property can be added to a collection of posts, indicating a corresponding collection of activities. This property is defined in FEP-bad1: Object history collection, although the use case here differs from the one described in that proposal.
contextHistory property","text":"contextHistory property can be used to make a reference from a post to a collection of activities.
After top-level post of a conversation is discovered, the whole conversation can be retrieved using the following algorithm:
contextHistory property is present, retrieve collection of activities and stop.context property is present, retrieve collection of posts and stop.replies property is present, retrieve collection of replies, and repeat this step for every reply.Collection of posts:
Collection of activities:
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/fb2a/","title":"FEP-fb2a: Actor metadata","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT 2022-12-09 #45 - codeberg"},{"location":"fep/fb2a/#summary","title":"Summary","text":"It is useful for actors to publish additional structured information about themselves without necessarily defining an extension property or additional vocabulary. This FEP describes a way for actors to publish generic key-value pairs representing their metadata.
"},{"location":"fep/fb2a/#history","title":"History","text":"Mastodon v2.4.0 (March 2018) implemented \"bio fields\" [1], a feature that allows adding structured data to profiles. This feature was federated via the attachment field, filtering for array items that had a type of PropertyValue derived from schema.org's vocabulary. Each item used name from the ActivityStreams Vocabulary, and value from the schema.org context. The schema.org namespace was defined as schema and (erroneously) mapped to http://schema.org# (instead of http://schema.org/ or https://schema.org) within the JSON-LD context property.
Misskey (December 2018) implemented \"user fields\" [2], following the same federation logic as Mastodon (filtering for a type of PropertyValue, then taking name and value).
Pleroma (August 2019) implemented \"custom profile fields\" [3], following the same federation logic as Mastodon (filtering for a type of PropertyValue, then taking name and value).
Rather than depending on an additional (and unnecessary) vocabulary, it makes sense to define a more \"native\" way of expressing the same idea of a key-value pair representing structured metadata about the actor. To this end, this FEP proposes using the existing Note type from the ActivityStreams 2.0 Vocabulary (instead of schema.org's PropertyValue), as well as the existing content property (instead of schema.org's value). Note that the name property exists within both the ActivityStreams 2.0 Vocabulary and the schema.org vocabulary, with largely the same semantic meaning; however, the use of schema.org vocabulary is out of scope of this FEP.
Thus, we can define a standard for actor metadata, largely drawing from prior art.
"},{"location":"fep/fb2a/#2-defining-generic-actor-metadata-as-key-value-pairs","title":"2. Defining generic actor metadata as key-value pairs","text":"General-purpose actor metadata fields SHOULD be included in the attachment array on the actor. If a more specific property exists and is a better fit for the specific metadata being expressed, then implementations MAY use that instead of or in addition to the more generic actor metadata.
Note.name representing the name (key) of the field.content representing the content (value) of the field.Actor metadata fields may also take the form of a link rather than a content value.
Link.href representing the value of the link.name representing a label for the link.rel values if an appropriate link relation exists, such as \"me\".(This section is non-normative.)
Existing implementations currently using the incorrect IRIs http://schema.org#PropertyValue and http://schema.org#value may wish to maintain backwards compatibility during a transitional period by serving both legacy representations as well as representations according to this FEP. The following algorithm may be used to support the legacy implementations while also favoring the implementation within this FEP:
attachment array for items of type Note or Link. Take name and content from each remaining item if the type is Note, or take name and href if the type is Link. If the type is Link and the rel contains \"me\", attempt to verify this link using rel-me verification.attachment), filter the attachment array for items of type http://schema.org#PropertyValue. Take name and http://schema.org#value from each remaining item. If name is a duplicate of an existing name, ignore the item.After some transitional period, implementations may wish to simplify their logic by filtering only for items of type Note and drop support for http://schema.org#PropertyValue, http://schema.org#value, and the schema.org context entirely (assuming those implementations do not use any other vocabulary from the schema.org context).
Consider a profile which has the following profile fields:
Pronouns\n: they/them\n\nMy portfolio\n: https://example.com/\n A legacy implementation might currently serialize these as such:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sc\": \"http://schema.org#\"\n \"PropertyValue\": \"sc:PropertyValue\",\n \"value\": \"sc:value\"\n }\n ],\n \"id\": \"https://social.example/someone\"\n \"type\": \"Person\",\n \"attachment\": [\n {\n \"type\": \"PropertyValue\",\n \"name\": \"Pronouns\",\n \"value\": \"they/them\"\n },\n {\n \"type\": \"PropertyValue\",\n \"name\": \"My portfolio\",\n \"value\": \"<a href=\"https://example.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">example.com</span><span class=\"invisible\"></span></a>\"\n }\n ]\n}\n For implementations that do not include the same incorrect IRI term mapping in their contexts, compaction would result in the following:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://social.example/someone\"\n \"type\": \"Person\",\n \"attachment\": [\n {\n \"type\": \"http://schema.org#PropertyValue\",\n \"name\": \"Pronouns\",\n \"http://schema.org#PropertyValue\": \"they/them\"\n },\n {\n \"type\": \"http://schema.org#PropertyValue\",\n \"name\": \"My portfolio\",\n \"http://schema.org#PropertyValue\": \"<a href=\"https://example.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">example.com</span><span class=\"invisible\"></span></a>\"\n }\n ]\n}\n Implementation according to this FEP might result in a simpler and more semantically correct serialization but equivalent representation like so:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://social.example/someone\"\n \"type\": \"Person\",\n \"attachment\": [\n {\n \"type\": \"Note\",\n \"name\": \"Pronouns\",\n \"content\": \"they/them\"\n },\n {\n \"type\": \"Link\",\n \"name\": \"My portfolio\",\n \"href\": \"https://example.com\",\n \"rel\": [\"nofollow\", \"noopener\", \"noreferrer\", \"me\"]\n }\n ]\n}\n"},{"location":"fep/fb2a/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/fe34/","title":"FEP-fe34: Origin-based security model","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2024-11-15 #445 Discussions codeberg"},{"location":"fep/fe34/#summary","title":"Summary","text":"Developing a comprehensive ActivityPub security framework based on the concept of web origin.
"},{"location":"fep/fe34/#rationale","title":"Rationale","text":"ActivityPub standard does not specify authentication and authorization mechanisms. However, in some cases it hints at the importance of an object's origin:
3. Objects
... Servers SHOULD validate the content they receive to avoid content spoofing attacks. (A server should do something at least as robust as checking that the object appears as received at its origin, but mechanisms such as checking signatures would be better if available).
7.3 Update Activity
... The receiving server MUST take care to be sure that the Update is authorized to modify its object. At minimum, this may be done by ensuring that the Update and its object are of same origin.
Implementations often rely on origin and ownership checks for determining the validity of activities and objects, but exact requirements are not documented and can be easily overlooked, leading to vulnerabilities such as GHSA-3fjr-858r-92rw.
This proposal attempts to formalize existing practices and provide guidance for implementers.
"},{"location":"fep/fe34/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/fe34/#assumptions","title":"Assumptions","text":"The origin-based security model is designed for use in a network where a server is resposible for enforcing security boundaries between the hosted actors. Servers that publish objects without validation are not supported.
Object identifiers are assumed to be HTTP(S) URIs. The model can also be used with other kinds of identifiers, but that is not covered in this document.
"},{"location":"fep/fe34/#origin","title":"Origin","text":"Object identifiers can be grouped together into protection domains called \"origins\". This concept is similar to the \"web origin\" concept described in RFC-6454, and origins of object IDs are computed by the same algorithm.
The same-origin policy determines when a relationship between objects can be trusted. Different origins are considered potentially hostile and are isolated from each other to varying degrees. Actors sharing an origin are assumed to trust each other because all their interactions are mediated by a single piece of software operated by a single person or an organization.
"},{"location":"fep/fe34/#comparing-origins","title":"Comparing origins","text":"uri-scheme be the scheme component of the URI, converted to lowercase.uri-host be the host component of the URI, converted to lowercase.uri-port be the default port for the protocol given by uri-scheme. Otherwise, let uri-port be the port component of the URI.(uri-scheme, uri-host, uri-port).Origins are the same if they have identical schemes, hosts, and ports.
"},{"location":"fep/fe34/#authentication","title":"Authentication","text":"Authentication is the process of verifying the origin of an ActivityPub object. It is performed in order to protect an application from spoofing attacks.
Objects can be authenticated using the following methods:
If an object can't be authenticated, it MUST be discarded.
Fetching from an origin is the primary authentication method, and other authentication methods described in this document depend on it. Consumers SHOULD attempt to fetch the object from its origin if other authentication methods are not available.
"},{"location":"fep/fe34/#fetching-from-an-origin","title":"Fetching from an origin","text":"Non-anonymous ActivityPub objects can be authenticated by making an HTTP GET request with object's ID as the target.
The last URI in the chain of redirects is object's location. The location SHOULD match the ID of the retrieved object. If object's location and ID are different, they MUST have the same origin.
If the object is protected, the server MAY require an HTTP signature.
Servers MUST validate all objects received from clients. Any activity representing an action that actor is not authorized to perform MUST be rejected. Special attention needs to be paid to media uploads, because malicious actors might attempt to bypass the validation by uploading ActivityPub documents as media. If the server allows clients to upload arbitrary files, it MUST serve media from a different origin (e.g. from a different subdomain).
As an additional protection in cases where an attacker was able to bypass the validation, consumers MUST verify that the response to a GET request contains the Content-Type header with the application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\" or application/activity+json media type (see GHSA-jhrq-qvrm-qr36 for more information).
Servers MUST NOT serve objects until they are validated.
"},{"location":"fep/fe34/#signatures","title":"Signatures","text":"Signature-based authentication can be used when:
The ID of the public key (or the verification method) MUST have the same origin as the object's ID.
Servers MUST NOT share secret keys with clients.
Servers MUST NOT allow clients to create or update objects representing public keys, including such objects embedded within actors and other objects. Public keys can be identified by their properties publicKeyPem and publicKeyMultibase. Embedded public keys with a different origin are permitted.
In order to minimize damage in the event of a key compromise or insufficient validation, consumers MUST verify that the signing key has the same owner as the signed object. Consumers MUST also confirm the ownership of the key by verifying a reciprocal claim.
Warning
JSON-LD consumers might be tricked into processing a specially crafted JSON object without publicKeyPem and publicKeyMultibase properties as a public key. Protections against attacks of that kind are not described in this document.
In some cases, an embedded object can be trusted when its wrapping object is trusted:
Authorization is the process of verifying permission to create, read, update or delete an object.
"},{"location":"fep/fe34/#ownership","title":"Ownership","text":"Ownership is indicated by a property of an ActivityPub object. The name of this property differs depending on the object class:
id property.actor property, which describes the actor that performed the activity. This actor is considered to be the owner of the activity.owner and controller properties.attributedTo property, which describes the actor to which the object is attributed. This actor is considered to be the owner of the object.In some cases ownership might be implicit. Examples:
replies collection is owned by the actor to which the post is attributed.Anonymous objects are not supposed to have an owner.
Applications SHOULD use the following algorithm to determine the owner of an object:
Link, return error.Object nor Collection, and the object has an attributedTo property, return error.Actor, return the value of the id property.VerificationMethod, return the value of the controller property.PublicKey, return the value of the owner property.Activity, return the value of the actor property.Object or Collection, return the value of the attributedTo property.The owner of an object MUST be an actor.
Identifier of an object and identifier of its owner MUST have the same origin.
Warning
According to Activity Vocabulary, actor and attributedTo properties can contain references to multiple actors. These scenarios are not covered by this document and implementers are expected to determine the appropriate authorization procedures on a case-by-case basis.
Owners are the same if their identifiers are identical after conversion of their schemes and hosts to lowercase.
"},{"location":"fep/fe34/#create-update-and-delete","title":"Create, update and delete","text":"The actor that creates an object MUST be its owner.
The owner of an object is permitted to modify and delete it. This permission might also be specified with reciprocal claims.
If the owner of an activity that modifies or deletes an object doesn't have a permission to perform the operation, the activity MUST be rejected. If such activity is received from another server and the permission can not be verified, the recipient MAY accept the activity if its actor and the owner of the affected object have the same origin.
Examples:
Update and Delete activities, and objects indicated by their object property are expected to have the same owner.Undo activity and object indicated by its object property are expected to have the same owner.Add and Remove activities, and objects indicated by their target property are expected to have the same owner.Announce and Like activities don't modify objects indicated by their object property, therefore their owners can be different.When a protected object is retrieved, the server MUST verify that the GET request contains an HTTP signature created using a key whose owner belongs to object's intended audience.
The server MAY require a signature even if the object is public. In that case, the request can be signed with a key owned by a server actor.
Servers that implement proxyUrl endpoint MUST ensure that access to objects is restricted to actors that belong to intended audiences of these objects.
"},{"location":"fep/fe34/#ownership-transfer","title":"Ownership transfer","text":"When ownership changes, the new owner ID MUST have the same origin as the old owner ID.
"},{"location":"fep/fe34/#reciprocal-claims","title":"Reciprocal claims","text":"Claims are considered reciprocal when one object specifies a claim that is accompanied by a reverse claim specified in another object.
Examples:
Relationships between objects with different origins are possible, but they MUST be confirmed with reciprocal claims made by both origins. In that case, the same-origin policy can be bypassed.
Examples:
Move activity if the migrating actor is included in the target actor's alsoKnownAs.CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/fffd/","title":"FEP-fffd: Proxy Objects","text":"Authors Status Date received Tracking issue Discussions Repository Adam R. Nelson adam@nels.onl, Ryan Barrett (administrative only)DRAFT 2023-04-29 #95 - codeberg"},{"location":"fep/fffd/#summary","title":"Summary","text":"A proxy object is an [ActivityPub] object that is semantically identical to another entity, which may exist on another, non-ActivityPub protocol. For example, an ActivityPub-to-Nostr bridge creates Actors and Notes that are proxies for Nostr users and notes.
This document describes a data format to identify proxy objects and to specify the ActivityPub and non-ActivityPub entities they are equivalent to, with the intention that multi-protocol clients will automatically merge objects with their proxies, hiding the implementation details of bridges and cross-protocol publishing from users.
"},{"location":"fep/fffd/#1-requirements","title":"1. Requirements","text":"The key words \"MUST\", \"SHOULD\", and \"MAY\" are to be interpreted as described in [RFC2119].
"},{"location":"fep/fffd/#2-rationale","title":"2. Rationale","text":"This section is non-normative.
Many Fediverse servers speak multiple protocols besides ActivityPub, such as Nostr or Diaspora, and simultaneously publish posts across multiple protocols. Additionally, bridge servers exist to relay posts between ActivityPub and other protocols such as Nostr.
A use case also exists for creating a duplicate of an ActivityPub object: moving an account to a new instance. Posts copied to a user's new account should be marked as duplicates of the originals, in a way that preserves the original posts' likes and replies.
Despite these use cases, there is currently no standard way to communicate that an ActivityPub activity is a copy (or proxy) of a post on another protocol.
Consider this scenario:
Proxy objects provide a potential solution to this problem.
"},{"location":"fep/fffd/#3-format","title":"3. Format","text":"FEP-fffd does not define any new vocabulary or @context entries. Instead, it further defines the meaning of Links in the url property of an Object when they have a rel property of \"alternate\" or \"canonical\".
Each Link in url with \"rel\": \"alternate\" or \"rel\": \"canonical\" is called a proxy link. Any Object with one or more proxy links is called a proxy object. The referent of a proxy link is called a proxied object, and SHOULD be considered semantically identical to the parent proxy object, modulo the limitations described in section 4.
A proxied object is not necessarily an ActivityPub object, or even an object accessible via a network request; its meaning is determined based on its protocol. The protocol and format of the proxied object are determined by the proxy link's URI scheme and mediaType; well-known protocols and defaults are defined in section 5. An application SHOULD ignore proxied objects in protocols or formats that the application does not understand.
If a proxy link has \"rel\": \"canonical\", it indicates that its proxied object is the canonical (original, authoritative) version of the proxy object. A proxy object MUST NOT have more than one proxy link with \"rel\": \"canonical\". This property SHOULD be used by bridges that relay posts made by third parties, to indicate that the bridged post is not the original. It SHOULD NOT be used by servers that publish to multiple protocols at once; in this case no one version of an object is more authoritative than another.
When a conforming application encounters a proxy object, it may merge it with its proxy objects under certain circumstances.
To merge a proxy object and its proxied object(s) means to display all of these objects as a single entity (such as a user or a post), while combining all collections and metadata belonging to these objects:
In some circumstances, an application may encounter malformed or malicious proxy links that could misrepresent objects not owned by the links' author, or it may encounter proxy links whose referents are malformed or missing. These situations sometimes prevent merging.
alsoKnownAs each other.alsoKnownAs each other.Several protocols are named in this document, but interaction with these protocols is left intentionally underspecified, as the behavior of non-ActivityPub protocols is outside the scope of this FEP. If a proxy link's URI scheme and/or mediaType match a protocol named in this section, a conforming application SHOULD either use the matching protocol to access the proxied object or ignore the proxy link entirely, but it MUST NOT interpret the link as a proxy link for a different protocol or format.
nostr: URI scheme, as defined in [NIP-21]. The identifiers used in these URIs MUST be \"bare\" NIP-19 identifiers starting with npub1 or note1. The npub1 identifier type MUST be used only in proxy links for Actors.diaspora: URI scheme, following the format defined in [diaspora:// URI scheme].did: URI scheme, as defined in [DID URL Syntax], and MUST be used only in proxy links for Actors.at URI scheme, as defined in [AT URI Scheme]. AT Repositories, Collections, and Records MUST be used only in proxy links for ActivityPub Actors, Collections, and Objects, respectively. Repository URIs starting with at://did: SHOULD be considered identical to the did: URIs they contain; including both a did: link and an at://did: link for the same DID is redundant.ssb: URI scheme.application/rss+xml; the href should be the URL of the feed, followed by a URL fragment whose content is the <guid> value of an entry in the feed.application/atom+xml; the href should be the URL of the feed, followed by a URL fragment whose content is the <id> value of an entry in the feed.application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\" or application/activity+json; the href should point to an ActivityPub Object.If an application supports general-purpose transport protocols other than HTTP(S), such as Gemini or IPFS, it MAY interpret proxy links to these protocols in the same manner as it would interpret HTTP(S) proxy links, including applying these well-known media types.
"},{"location":"fep/fffd/#53-non-federated-web-content","title":"5.3. Non-federated Web Content","text":"By default, if a proxy link uses the http or https protocol, and either does not have a mediaType or has a mediaType of text/html, it is considered a link to some unspecified, application-defined non-federated Web content. An application MAY interpret this link as any kind of content or protocol other than one of the well-known protocols or formats defined in this section. Notably, this kind of proxy object MUST NOT be interpreted as an ActivityPub resource, even if the link responds with valid ActivityStreams data.
This default allows an ActivityPub object to proxy any non-federated Web content; for example, a Twitter-to-ActivityPub bridge may use a proxy link to a Twitter URL to identify a Note as a proxy object for a Twitter post.
"},{"location":"fep/fffd/#6-examples","title":"6. Examples","text":"This section is non-normative. The JSON-LD @context property is omitted for brevity.
A post relayed by a third-party Twitter-to-ActivityPub bridge. Because the canonical proxy link is also the only url entry, it should also be used as a clickable link to the original post.
{\n \"id\": \"http://twitter-bridge.example/status/1234\",\n \"type\": \"Note\",\n \"actor\": \"http://twitter-bridge.example/@jack\",\n \"content\": \"just setting up my twttr\",\n \"url\": {\n \"type\": \"Link\",\n \"rel\": \"canonical\",\n \"href\": \"https://twitter.com/jack/status/20\"\n }\n}\n A post published to ActivityPub, Diaspora, and Nostr simultaneously. Because there is one non-proxy Link in url with an https protocol, this non-proxy link should be used as a clickable link to the original post.
{\n \"id\": \"http://fediverse.example/status/1234\",\n \"type\": \"Note\",\n \"actor\": \"http://fediverse.example/@alice\",\n \"content\": \"Hello, world!\",\n \"url\": [{\n \"type\": \"Link\",\n \"href\": \"https://fediverse.example/@alice/1234\"\n }, {\n \"type\": \"Link\",\n \"rel\": \"alternate\",\n \"href\": \"diaspora://alice@fediverse.example/post/deadbeefdeadbeefdeadbeefdeadbeef\"\n }, {\n \"type\": \"Link\",\n \"rel\": \"alternate\",\n \"href\": \"nostr:note1gwdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef\"\n }]\n}\n An ActivityPub Note copied from a user's old instance to a new instance when moving to a new account.
{\n \"id\": \"http://newinstance.example/status/1234\",\n \"type\": \"Note\",\n \"actor\": \"http://newinstance.example/@alice\",\n \"content\": \"Hello, world!\",\n \"url\": [{\n \"type\": \"Link\",\n \"href\": \"https://newinstance.example/@alice/1234\"\n }, {\n \"type\": \"Link\",\n \"rel\": \"canonical\",\n \"mediaType\": \"application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"\",\n \"href\": \"https://oldinstance.example/status/5678\"\n }]\n}\n"},{"location":"fep/fffd/#references","title":"References","text":"nostr: URI scheme, 2023diaspora:// URI scheme] Benjamin Neff, diaspora* federation protocol, 2017CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"],"fields":{"title":{"boost":1000.0},"text":{"boost":1.0},"tags":{"boost":1000000.0}}},"docs":[{"location":"","title":"Fediverse Enhancement Proposals","text":"A Fediverse Enhancement Proposal (FEP) is a document that provides information to the Fediverse community. The goal of a FEP is to improve interoperability and well-being of diverse services, applications and communities that form the Fediverse.
The FEP Process is an initiative of the SocialHub developer community, a liaison of the W3C Social Web Incubator Community Group. For ongoing and past discussion see the SocialHub FEP category.
"},{"location":"#submitting-a-fep","title":"Submitting a FEP","text":"Do you have an idea, opinion or information that you want to share with the wider Fediverse community? You may do so with a Fediverse Enhancement Proposal (FEP).
To create and submit a FEP:
$ echo -n \"The title of my proposal\" | sha256sum | cut -c-4\nb3f0\n fep/ using the identifier you just computed.Use the identifer as the \"slug\" when filling out the frontmatter.
abcd, then your file would be located at fep/abcd/fep-abcd.md and your frontmatter would include slug: \"abcd\".Write down your idea in the newly created file and commit it to a new branch in your repository (ex. fep-xxxx).
dateReceived field in the frontmatter to the current date.discussionsTo field containing the URL of the discussion topic to the front matter of your FEP.Alternatively to steps 3. to 6., you can run
./scripts/new_proposal.py TITLE OF YOUR PROPOSAL\n that should create a prefilled template for you.
"},{"location":"#facilitators","title":"Facilitators","text":"The list of FEP's is maintained by the facilitators who are listed in the FACILITATORS.md file. Facilitators are neutral custodians of the FEP process, who merge PR's and create tracking issues.
"},{"location":"#contributing","title":"Contributing","text":"Do you have ideas to improve the FEP Process? Post your suggestions to the issue tracker, or on the SocialHub forum. The SocialHub developer community is a \"DoOcracy\" which means: \u201cpick up any task you want, and then steer it to completion\u201d. Your contributions are most welcome, so delve in and find out how you can help.
"},{"location":"#license","title":"License","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this document have waived all copyright and related or neighboring rights to this work.
"},{"location":"draft/","title":"Draft","text":"Title Tracking issue Discussions Received FEP-8b32: Object Integrity Proofs #29 Discussions 2022-11-12 FEP-c390: Identity Proofs #34 Discussions 2022-11-23 FEP-fb2a: Actor metadata #45 - 2022-12-09 FEP-c118: Content licensing support #57 - 2023-01-16 FEP-7888: Demystifying the context property #68 Discussions 2023-03-14 FEP-888d: Using https://w3id.org/fep as a base for FEP-specific namespaces #83 Discussions 2023-04-10 FEP-0ea0: Payment Links #88 Discussions 2023-04-18 FEP-fffd: Proxy Objects #95 - 2023-04-29 FEP-4adb: Dereferencing identifiers with webfinger #100 - 2023-05-13 FEP-a070: Ordered properties for plain JSON consumers #121 - 2023-06-13 FEP-c648: Blocked Collection #123 - 2023-06-14 FEP-bad1: Object history collection #124 - 2023-06-15 FEP-4ccd: Pending Followers Collection and Pending Following Collection #129 Discussions 2023-06-21 FEP-d36d: Sharing Content Across Federated Forums #125 - 2023-07-01 FEP-1970: Chat Links #128 - 2023-07-04 FEP-ae97: Client-side activity signing #148 Discussions 2023-08-14 FEP-0837: Federated Marketplace #147 Discussions 2023-08-17 FEP-5feb: Search indexing consent for actors #154 - 2023-09-06 FEP-dc88: Formatting Mathematics #161 - 2023-09-12 FEP-d8c2: OAuth 2.0 Profile for the ActivityPub API #165 Discussions 2023-09-17 FEP-7628: Move actor #168 Discussions 2023-09-20 FEP-37f2: a policy for calls for consensus on SWICG group decisions #184 - 2023-09-28 FEP-2677: Identifying the Application Actor #190 - 2023-10-14 FEP-03c1: Actors without acct-URI #205 - 2023-11-10 FEP-ef61: Portable Objects #209 Discussions 2023-12-06 FEP-7502: Limiting visibility to authenticated actors #218 - 2023-12-24 FEP-2c59: Discovery of a Webfinger address from an ActivityPub actor #226 - 2024-01-04 FEP-3264: Federated Work Coordination #255 - 2024-01-31 FEP-c5a1: To-do's #256 - 2024-01-31 FEP-61cf: The OpenWebAuth Protocol #263 - 2024-02-06 FEP-73cd: Migration User Stories #265 - 2024-02-07 FEP-96ff: Explicit signalling of ActivityPub Semantics #272 - 2024-02-17 FEP-9fde: Mechanism for servers to expose supported operations #282 - 2024-03-14 FEP-e229: Best practices for extensibility #293 - 2024-04-02 FEP-3b86: Activity Intents #302 - 2024-04-19 FEP-7458: Using the replies collection #307 - 2024-04-26 FEP-0391: Special collection proofs #310 - 2024-04-29 FEP-db0e: Authentication mechanism for non-public groups #313 - 2024-05-03 FEP-cd47: Federation-friendly Addressing and Deduplication Use-Cases #319 Discussions 2024-05-31 FEP-5e53: Opt-out Preference Signals #327 Discussions 2024-06-09 FEP-7952: Roadmap For Actor and Object Portability #435 Discussions 2024-06-20 FEP-e3e9: Actor-Relative URLs #346 - 2024-06-29 FEP-d9ad: Create Conformance Tests for Fediverse Enhancement Proposals #350 - 2024-07-02 FEP-e965: Move Activity for Migrations and Announce Activity for Tombstone Events #352 Discussions 2024-07-05 FEP-9091: Export Actor Service Endpoint #358 Discussions 2024-07-08 FEP-6fcd: Account Export Container Format #357 Discussions 2024-07-11 FEP-c551: Use ECMAScript Modules to Create Conformance Tests for Fediverse Enhancement Proposals #366 - 2024-07-11 FEP-a5c5: Web Syndication Methods #368 Discussions 2024-07-15 FEP-c4ad: Viewership History #367 Discussions 2024-07-15 FEP-c893: DOAP #372 Discussions 2024-07-15 FEP-eb48: Hashtags #373 Discussions 2024-07-16 FEP-eb22: Supported ActivityStreams types with NodeInfo #386 Discussions 2024-07-25 FEP-c0e0: Emoji reactions #384 Discussions 2024-08-08 FEP-c16b: Formatting MFM functions #383 Discussions 2024-08-10 FEP-0499: Delivering to multiple inboxes with a multibox endpoint #402 Discussions 2024-09-30 FEP-76ea: Conversation Threads #407 Discussions 2024-10-04 FEP-1985: Signaling how an OrderedCollection is ordered #417 Discussions 2024-10-10 FEP-268d: Search consent signals for objects #421 Discussions 2024-10-12 FEP-b2b8: Long-form Text #441 Discussions 2024-11-07 FEP-fe34: Origin-based security model #445 Discussions 2024-11-15 FEP-171b: Conversation Containers #449 Discussions 2024-11-23 FEP-6606: ActivityPub client to server collections addressing conventions #453 Discussions 2024-12-04 FEP-1311: Media Attachments #459 Discussions 2024-12-08 FEP-7d8c: Documentation: Automation of FEP #470 Discussions 2025-01-20 FEP-9967: Polls #478 Discussions 2025-01-23 FEP-2277: ActivityPub core types #485 Discussions 2025-01-31 FEP-a974: All Actor types should be followable #487 Discussions 2025-02-05 FEP-efda: Followable objects #492 Discussions 2025-02-13 FEP-f228: Backfilling conversations #500 Discussions 2025-02-17 FEP-f06f: Object observers #503 Discussions 2025-02-18 FEP-dd4b: Quote Posts #511 Discussions 2025-02-21 FEP-c180: Problem Details for ActivityPub #531 Discussions 2025-03-11 FEP-2931: Representing context with a Collection #534 Discussions 2025-03-22 FEP-5711: Inverse Properties for Collections #539 Discussions 2025-03-24 FEP-044f: Consent-respecting quote posts #547 Discussions 2025-04-03 FEP-1042: Peer to Peer Fediverse Identities #546 Discussions 2025-04-03 FEP-4f05: Soft Deletion #554 Discussions 2025-04-15 FEP-8a8e: A common approach to using the Event object type #565 Discussions 2025-04-23 FEP-0151: NodeInfo in Fediverse Software (2025 edition) #599 Discussions 2025-05-12 FEP-82f6: Actor statuses #600 Discussions 2025-05-12 FEP-844e: Capability discovery #624 Discussions 2025-06-14 FEP-b06c: ActivityPoll #638 Discussions 2025-06-25 FEP-9098: Custom emojis #648 Discussions 2025-07-06 FEP-11dd: Context Ownership and Inheritance #675 Discussions 2025-09-11 FEP-8967: Generating link previews for attached links #681 Discussions 2025-09-16 FEP-1580: Move Actor Objects with amigration Collection #702 Discussions 2025-10-09 FEP-d8c8: BitTorrent Torrent Objects #722 Discussions 2025-11-03 FEP-19b3: Specifying Properties of a Service #719 Discussions 2025-11-04 FEP-22b6: Linking an ActivityPub Object to a HTML page and back #730 Discussions 2025-11-12 FEP-f15d: Context Relocation and Removal #751 Discussions 2026-01-12 FEP-ee3a: Exif metadata support #752 Discussions 2026-01-13"},{"location":"final/","title":"Final","text":"Title Tracking issue Discussions Received Finalized FEP-a4ed: The Fediverse Enhancement Proposal Process #201 - 2020-10-16 2021-01-18 FEP-8fcf: Followers collection synchronization across servers #6 - 2020-10-24 2022-02-07 FEP-f1d5: NodeInfo in Fediverse Software #50 - 2020-12-13 2023-06-02 FEP-400e: Publicly-appendable ActivityPub collections #5 - 2021-02-16 2022-02-04 FEP-e232: Object Links #14 Discussions 2022-08-01 2023-12-03 FEP-1b12: Group federation #22 - 2022-11-12 2023-02-09 FEP-521a: Representing actor's public keys #130 Discussions 2023-07-08 2025-06-14 FEP-67ff: FEDERATION.md #157 Discussions 2023-09-05 2024-09-22 FEP-d556: Server-Level Actor Discovery Using WebFinger #243 - 2024-01-20 2025-03-15 FEP-ae0c: Fediverse Relay Protocols: Mastodon and LitePub #424 Discussions 2024-10-19 2025-03-14"},{"location":"withdrawn/","title":"Withdrawn","text":"Title Tracking issue Discussions Received Withdrawn FEP-8c3f: Web Monetization #3 - 2022-01-18 2023-10-27 FEP-2100: Unbound Group and Organization #36 Discussions 2022-03-31 2025-04-06 FEP-5624: Per-object reply control policies #18 Discussions 2022-08-23 2025-06-24 FEP-cb76: Content Addressed Vocabulary #41 Discussions 2022-11-29 2025-04-07 FEP-2e40: The FEP Vocabulary Extension Process #62 - 2023-02-13 2023-09-04 FEP-d767: Extend ActivityPub with Valueflows #76 - 2023-04-02 2024-02-03 FEP-5bf0: Collection sorting and filtering #80 - 2023-04-10 2025-06-14 FEP-612d: Identifying ActivityPub Objects through DNS #89 - 2023-04-18 2025-10-10 FEP-07d7: A Custom URL Scheme and Web-Based Protocol Handlers for Linking to ActivityPub Resources #171 - 2023-09-22 2025-04-22 FEP-6481: Specifying ActivityPub extension support with NodeInfo #279 - 2024-03-12 2024-10-31 FEP-c7d3: Ownership #323 Discussions 2024-06-04 2025-02-21"},{"location":"fep/0151/","title":"FEP-0151: NodeInfo in Fediverse Software (2025 edition)","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT implementation 2025-05-12 #599 Discussions codeberg"},{"location":"fep/0151/#summary","title":"Summary","text":"NodeInfo is a protocol intended to standardize upon a way to provide server-level metadata to the public. This enables tools and clients to utilize this metadata to assess server health or facilitate end-users choices about servers and software to use on the Fediverse.
This document is a revised version of FEP-f1d5: NodeInfo in Fediverse Software, which was published in 2020.
"},{"location":"fep/0151/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/0151/#nodeinfo-best-practices","title":"NodeInfo best practices","text":"Servers SHOULD implement NodeInfo protocol version 2.1.
"},{"location":"fep/0151/#privacy-and-security-considerations","title":"Privacy and security considerations","text":"Some information contained in the NodeInfo document might be considered sensitive by server operators.
The NodeInfo endpoint could be disabled to hide that information.
"},{"location":"fep/0151/#hiding-usage-statistics","title":"Hiding usage statistics","text":"All numeric properties in the usage object are optional and could be omitted to hide usage statistics. Note that users property is not optional:
{\n \"usage\": {\n \"users\": {}\n }\n}\n Servers MUST NOT publish incorrect usage statistics.
"},{"location":"fep/0151/#capability-detection","title":"Capability detection","text":"Servers SHOULD NOT rely on NodeInfo data for protocol negotiation and capability detection because server operators might intentionally disable NodeInfo. Servers MUST NOT infer capabilities from the software field and MUST treat software.name and software.version values as opaque strings.
Protocol-specific capability discovery mechanisms could be used instead. The capabilities of an ActivityPub application can be advertised using a server actor, as described in FEP-844e: Capability discovery.
"},{"location":"fep/0151/#metadata","title":"metadata","text":"Properties of the metadata object are not standardized, but some of them are widely used:
nodeName (string): The name of the server.nodeDescription (string): The long form description of the server.staffAccounts (array[string]): The list of staff account IDs (actor IDs for ActivityPub).federation.enabled (boolean): Whether the server is connected to other servers.Example:
{\n \"metadata\": {\n \"nodeName\": \"My server\",\n \"nodeDescription\": \"My server\",\n \"staffAccounts\": [\n \"https://social.example/actors/1\"\n ],\n \"federation\": {\n \"enabled\": true\n }\n }\n}\n"},{"location":"fep/0151/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/0391/","title":"FEP-0391: Special collection proofs","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT 2024-04-29 #310 - codeberg"},{"location":"fep/0391/#summary","title":"Summary","text":"Some properties represent special collections, such as:
outbox (ActivityPub)inbox (ActivityPub)followers (ActivityPub)following (ActivityPub)liked (ActivityPub)likes (ActivityPub)shares (ActivityPub)replies (FEP-7458)context (FEP-7888)Verifying that any given object is part of a special collection is usually only possible by resolving that collection and checking its items one-by-one until the current object is found. This can be inefficient to verify. It would be easier if there was an inverse claim for each claim made about an object being part of a special collection. This FEP aims to define some properties that can be used to make those inverse claims.
"},{"location":"fep/0391/#mapping-claims-and-inverse-claims","title":"Mapping claims and inverse claims","text":"Given the special collections above, we can map the following claims and inverse claims. The last two inverse claims are particularly salient, since inclusion in replies and/or context carries additional semantic meaning and is a socially loaded relation used to establish conversational constructs.
outbox","text":"Claim : Activity <A>'s actor (<A>.actor) is actor <B> Inverse claim : Actor <B>'s outbox collection (<B>.outbox) contains activity <A>
This is not particularly useful to prove.
"},{"location":"fep/0391/#inbox-claim","title":"Verifying presence ininbox","text":"Claim : Activity <A> has to/cc/audience including actor <B> Inverse claim : Actor <B>'s inbox collection (<B>.inbox) contains activity <A>
This is not particularly useful to prove, and at best can only be implied if <B> is addressed directly and not through some collection that triggers inbox forwarding.
followers","text":"Claim : Actor <B>'s following collection (<B>.following) contains actor <A>
Equivalent claim : Actor <B> claims that <B> is following <A>
Another equivalent claim : Actor <B> claims that <A> is followed by <B>
Inverse claim : Actor <A>'s followers collection (<A>.followers) contains actor <B>
Equivalent inverse claim : Actor <A> claims that <A> is followed by <B>
Another equivalent inverse claim : Actor <A> claims that <B> is following <A>
This can be verified by showing one of the following:
<A>.followers includes <B> as wellattributedTo is <A>subject is <B>relationship is IsFollowingobject is <A>attributedTo is <A>subject is <A>relationship is IsFollowedByobject is <B>actor is <A>type is Acceptobject.actor is <B>object.type is Followobject.object is <A>actor is <A>type is Addobject is <B>target is <A>.followersfollowing","text":"Claim : Actor <B>'s followers collection (<B>.followers) contains actor <A>
Equivalent claim : Actor <B> claims that <B> is followed by <A>
Another equivalent claim : Actor <B> claims that <A> is following <B>
Inverse claim : Actor <A>'s following collection (<A>.following) contains actor <B>
Equivalent inverse claim : Actor <A> claims that <A> is following <B>
Another equivalent inverse claim : Actor <A> claims that <B> is followed by <A>
This can be verified by showing one of the following:
<A>.following includes <B> as wellattributedTo is <A>subject is <A>relationship is IsFollowingobject is <B>attributedTo is <A>subject is <B>relationship is IsFollowedByobject is <A>actor is <B>type is Acceptobject.actor is <A>object.type is Followobject.object is <B>object is a trusted activityactor is <A>type is Addobject is <B>target is <A>.followingliked","text":"Claim : Actor <A>'s liked collection (<A>.liked) contains object <O>
This can be verified by showing a trusted activity exists in <O>.likes where: - actor is <A> - type is Like - object is <O>
Note that there is an issue that may occur if <A> issues multiple Like activities for the same object <O>, and then issues any Undo Like activities at a later point in time. The most recent activity will have its side-effects carried out. It is possible for some of these functionally duplicate Like activites to remain in <O>.likes even though the object <O> is no longer in <A>.liked. See https://github.com/w3c/activitypub/issues/381 for more information.
likes","text":"Claim : Object <O>'s likes collection (<O>.likes) contains a Like activity <L> from actor <A>
If <L> is not already trusted via some other mechanism, this can be verified by showing that <A>.liked includes <O>. Note the duplication issue from the previous section.
The Like activity <L> MAY result in an Add activity <R> targeting <O>.likes. This result can be used by <A> as proof of the claim, if it can be shown to be a trusted activity.
shares","text":"Claim : Object <O>'s shares collection (<O>.shares) contains an Announce activity <S> from actor <A>
The Announce activity <S> MAY result in an Add activity <R> targeting <O>.shares. This result can be used by <A> as proof of the claim, if it can be shown to be a trusted activity.
replies","text":"Claim : Object <R> is inReplyTo object <O>
Inverse claim : Object <O>'s replies collection (<O>.replies) contains object <R> attributed to actor <A>
The inverse claim can be verified by showing that an activity exists where: - actor is <O>.attributedTo - type set includes Add - object is <R> - target is <O>.replies
context","text":"Claim : Object <O> has a context pointing to context collection <C>
Inverse claim : Context collection <C> contains object <O>
The inverse claim can be verified by showing that an activity exists where: - actor is <O>.context.attributedTo - type set includes Add - object is <R> - target is <O>.context
We can map proofs onto the result property provided that its subject is an activity and its referent is a trusted activity. We may also define some extension properties for non-activity objects.
replies collections via a reply stamp","text":"Provides proof that some object was added to some replies collection.
On an activity where inReplyTo has been set, the proof can be verified if all of the following are satisfied:
result.actor MUST be included in either inReplyTo.actor or inReplyTo.attributedToresult.type MUST include Addresult.object MUST be equivalent to the current activityresult.target MUST be equivalent to inReplyTo.repliesOn a Create activity where the object has inReplyTo set, the proof can be verified if all of the following are satisfied:
result.actor MUST be included in either object.inReplyTo.attributedTo or object.inReplyTo.actorresult.type MUST include Addresult.object MUST be equivalent to objectresult.target MUST be equivalent to object.inReplyTo.repliesOn a non-activity Object where inReplyTo has been set, we define the extension property inReplyToProof since the use of result is invalid on non-Activity types. The proof is valid if all of the following are satisfied:
inReplyToProof.actor MUST be included in either inReplyTo.attributedTo or inReplyTo.actorinReplyToProof.type MUST include AddinReplyToProof.object MUST be equivalent to the current objectinReplyToProof.target MUST be equivalent to inReplyTo.repliesExample of an activity with inReplyTo set to some activity with a replies collection:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-activity\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Activity\",\n \"object\": \"https://example.com/some-object\",\n \"inReplyTo\": {\n \"id\": \"https://example.com/some-other-create\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Create\",\n \"object\": \"https://example.com/some-other-object\",\n \"content\": \"I am accepting replies to this activity.\",\n \"replies\": \"https://example.com/some-other-create/replies\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-activity\",\n \"target\": \"https://example.com/some-other-create/replies\",\n \"attributedTo\": \"https://example.com/some-activity\"\n }\n}\n Example of a Create activity with object.inReplyTo set to some non-activity object with a replies collection:
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/create-some-reply\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Create\",\n \"object\": {\n \"id\": \"https://example.com/some-reply\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/actors/2\",\n \"content\": \"This is a reply, and I can prove it was added to the replies collection.\",\n \"inReplyTo\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/actors/1\",\n \"content\": \"I am accepting replies to this object.\",\n \"replies\": \"https://example.com/some-object/replies\"\n },\n \"inReplyToProof\": \"https://example.com/some-proof\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-reply\",\n \"target\": \"https://example.com/some-object/replies\",\n \"attributedTo\": \"https://example.com/create-some-reply\"\n }\n}\n"},{"location":"fep/0391/#context-stamp","title":"Showing inclusion in context collections via a context stamp","text":"Provides proof that some object was added to some context collection.
On an activity where the context has been set to a collection, the proof can be verified if all of the following are satisfied:
result.actor MUST be included in context.attributedToresult.type MUST include Addresult.object MUST be equivalent to the current activityresult.target MUST be equivalent to contextOn a Create activity where the object has context set, the proof can be verified if all of the following are satisfied:
result.actor MUST be included in object.context.attributedToresult.type MUST include Addresult.object MUST be equivalent to objectresult.target MUST be equivalent to object.contextOn a non-activity Object where context has been set, we define the extension property contextProof since the use of result is invalid on non-Activity types. The proof is valid if all of the following are satisfied:
contextProof.actor MUST be included in context.attributedTocontextProof.type MUST include AddcontextProof.object MUST be equivalent to the current objectcontextProof.target MUST be equivalent to contextExample of an activity with context set to some owned collection:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-activity\",\n \"actor\": \"https://example.com/some-actor\",\n \"type\": \"Activity\",\n \"object\": \"https://example.com/some-object\",\n \"context\": {\n \"id\": \"https://example.com/some-context\",\n \"type\": \"Collection\",\n \"attributedTo\": \"https://example.com/some-context-moderator\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/some-context-moderator\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-activity\",\n \"target\": \"https://example.com/some-context\",\n \"attributedTo\": \"https://example.com/some-activity\"\n }\n}\n Example of a Create activity with object.context set to some owned collection:
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/create-some-object\",\n \"actor\": \"https://example.com/some-actor\",\n \"type\": \"Create\",\n \"object\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/some-actor\",\n \"content\": \"This object is part of some context, and I can prove it was added to the context collection.\",\n \"context\": {\n \"id\": \"https://example.com/some-context\",\n \"type\": \"Collection\",\n \"attributedTo\": \"https://example.com/some-context-moderator\"\n },\n \"contextProof\": \"https://example.com/some-proof\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/some-context-moderator\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-object\",\n \"target\": \"https://example.com/some-context\",\n \"attributedTo\": \"https://example.com/create-some-object\"\n }\n}\n"},{"location":"fep/0391/#likes-stamp","title":"Showing inclusion in likes collections via a like stamp","text":"Provides proof that the current activity was added to the object.likes collection.
On a Like activity where the object has a likes collection, the proof can be verified if all of the following are satisfied:
result.actor MUST be included in object.attributedToresult.type MUST include Addresult.object MUST be equivalent to the current activityresult.target MUST be equivalent to object.likesExample:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-like\",\n \"summary\": \"A Like activity, with proof that it was added to the likes collection.\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Like\",\n \"object\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"content\": \"I am accepting likes of this object.\",\n \"likes\": \"https://example.com/some-object/likes\",\n \"attributedTo\": \"https://example.com/actors/1\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-like\",\n \"target\": \"https://example.com/some-object/likes\",\n \"attributedTo\": \"https://example.com/some-like\"\n }\n}\n"},{"location":"fep/0391/#shares-stamp","title":"Showing inclusion in shares collections via a share stamp","text":"Provides proof that the current activity was added to the object.shares collection.
On an Announce activity where the object has a shares collection, the proof can be verified if all of the following are satisfied:
result.actor MUST be included in object.attributedToresult.type MUST include Addresult.object MUST be equivalent to the current activityresult.target MUST be equivalent to object.sharesExample:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-announce\",\n \"summary\": \"An Announce activity, with proof that it was added to the shares collection.\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Announce\",\n \"object\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"content\": \"I am accepting shares of this object.\",\n \"shares\": \"https://example.com/some-object/shares\",\n \"attributedTo\": \"https://example.com/actors/1\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-announce\",\n \"target\": \"https://example.com/some-object/shares\",\n \"attributedTo\": \"https://example.com/some-announce\"\n }\n}\n"},{"location":"fep/0391/#relationship-proofs","title":"Relationship proofs","text":"Warning
Experimental, requires further thought.
Warning
Currently bugged. See https://github.com/w3c/activitystreams/issues/593 for more details.
Provides proof that the current relationship is reciprocally claimed.
Relationships other than following or being a follower may be proved using this property, but the requirements for such a proof are out of scope of this FEP.
We define the extension property relationshipProof since the use of result is invalid on non-Activity types. The proof is valid if all of the following are satisfied:
Example that proves a user is following another user:
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/some-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/1\",\n \"subject\": {\n \"id\": \"https://example.com/actors/1\",\n \"following\": \"https://example.com/actors/1/following\"\n },\n \"relationship\": \"IsFollowing\",\n \"object\": {\n \"id\": \"https://example.com/actors/2\",\n \"followers\": \"https://example.com/actors/2/followers\"\n },\n \"relationshipProof\": [\n {\n \"id\": \"https://example.com/not-enough-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/2\",\n \"target\": \"https://example.com/actors/1/following\"\n },\n {\n \"id\": \"https://example.com/proof-by-inverse-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/2\",\n \"subject\": \"https://example.com/actors/2\",\n \"relationship\": \"IsFollowedBy\",\n \"object\": \"https://example.com/actors/1\"\n },\n {\n \"id\": \"https://example.com/proof-by-being-added-to-followers\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/1\",\n \"target\": \"https://example.com/actors/2/followers\"\n },\n {\n \"id\": \"https://example.com/proof-by-having-follow-accepted\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Accept\",\n \"object\": {\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Follow\",\n \"object\": \"https://example.com/actors/2\"\n }\n }\n ]\n}\n Example that proves a user is followed by another user:
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/some-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/1\",\n \"subject\": {\n \"id\": \"https://example.com/actors/1\",\n \"followers\": \"https://example.com/actors/1/following\"\n },\n \"relationship\": \"IsFollowedBy\",\n \"object\": {\n \"id\": \"https://example.com/actors/2\",\n \"following\": \"https://example.com/actors/2/followers\"\n },\n \"relationshipProof\": [\n {\n \"id\": \"https://example.com/not-enough-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/2\",\n \"target\": \"https://example.com/actors/1/followers\"\n },\n {\n \"id\": \"https://example.com/proof-by-inverse-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/2\",\n \"subject\": \"https://example.com/actors/2\",\n \"relationship\": \"IsFollowing\",\n \"object\": \"https://example.com/actors/1\"\n },\n {\n \"id\": \"https://example.com/proof-by-being-added-to-following\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/1\",\n \"target\": \"https://example.com/actors/2/following\"\n },\n {\n \"id\": \"https://example.com/proof-by-having-follow\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Follow\",\n \"object\": \"https://example.com/actors/1\"\n }\n ]\n}\n Miscellaneous examples:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-follow\",\n \"actor\": {\n \"id\": \"https://example.com/actors/2\",\n \"following\": \"https://example.com/actors/2/following\"\n },\n \"type\": \"Follow\",\n \"object\": {\n \"id\": \"https://example.com/actors/1\",\n \"followers\": \"https://example.com/actors/1/followers\"\n },\n \"result\": {\n \"id\": \"https://example.com/accept-follow\",\n \"actor\": \"https://example.com/actor/1\",\n \"type\": \"Accept\",\n \"object\": \"https://example.com/some-follow\",\n \"result\": [\n {\n \"id\": \"https://example.com/resulting-add-to-followers\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/2\",\n \"target\": \"https://example.com/actors/1/followers\",\n \"attributedTo\": \"https://example.com/accept-follow\"\n },\n {\n \"id\": \"https://example.com/resulting-add-to-following\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/1\",\n \"target\": \"https://example.com/actors/2/following\",\n \"attributedTo\": \"https://example.com/accept-follow\"\n }\n ]\n }\n}\n"},{"location":"fep/0391/#root-trust","title":"Existing mechanisms of trust","text":"Verifying stamps has an issue with bootstrapping trust. In addition to verifying stamps via logical inference, ActivityPub clients SHOULD also consider the following:
Upon receiving an activity with a side effect of adding something to a special collection, ActivityPub servers SHOULD generate and deliver an Add activity representing this side-effect. The ActivityPub server MAY require manual action by a user. The resulting stamp activity SHOULD be attributedTo the activity that caused the side-effect, delivered to its actor, and additionally SHOULD either be resolvable (to allow direct same-origin checking) or otherwise include a cryptographic proof. If a cryptographic proof is included, the proof SHOULD expire after some reasonable window, beyond which point a new proof should be issued. If the resulting stamp activity is resolvable, it MAY become unresolvable after some time; HTTP caching SHOULD be used to indicate a time-to-live for ActivityPub clients to re-check cached stamps. The time-to-live MAY be used as the duration of the cryptographic proof, if one exists.
The recipient of a stamp SHOULD Update their object to include the appropriate property for the stamp -- result if it is an activity, inReplyToProof if it is a non-activity object that declares inReplyTo, contextProof if it is a non-activity object that declares context. [TODO: how to handle relationship proofs?]
Revoking a stamp can be done by: - If it is resolvable, making it no longer resolve - If it is signed, then waiting for the signature to expire - Update the stamp with some property to signal that the Add activity has been undone. [TODO: flesh this out more -- how does this work exactly? should it use Remove? Tombstone? Undo? how does this interact with outbox and the activity history? i'm thinking Update -> Tombstone, or extension like \"undoneBy\"]
This flow is similar for any stamp that uses result on an activity -- Like, Announce, Activity with inReplyTo, Activity with context.
Actor 2 likes a post by actor 1:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-like\",\n \"summary\": \"Actor 2 liked a Note by actor 1\",\n \"actor\": {\n \"id\": \"https://example.com/actors/2\",\n \"followers\": \"https://example.com/actors/2/followers\"\n },\n \"type\": \"Like\",\n \"object\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"content\": \"I am accepting likes of this object.\",\n \"likes\": \"https://example.com/some-object/likes\",\n \"attributedTo\": {\n \"id\": \"https://example.com/actors/1\",\n \"followers\": \"https://example.com/actors/1/followers\"\n },\n \"cc\": [\n \"https://example.com/actors/1/followers\",\n \"as:Public\"\n ],\n \"audience\": \"https://example.com/some-object/audience\"\n },\n \"to\": \"https://example.com/actors/1\",\n \"cc\": [\n \"https://example.com/actors/2/followers\",\n \"https://example.com/some-object/audience\",\n \"as:Public\"\n ],\n \"audience\": \"https://example.com/some-like/audience\"\n}\n Actor 1 sends a like stamp:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-proof\",\n \"summary\": \"Actor 1 approved a like\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-like\",\n \"target\": \"https://example.com/some-object/likes\",\n \"attributedTo\": \"https://example.com/some-like\",\n \"to\": \"https://example.com/actors/2\",\n \"cc\": [\n \"https://example.com/actors/2/followers\",\n \"https://example.com/some-like/audience\",\n \"as:Public\"\n ]\n}\n Actor 2 updates their Like activity with proof:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-update\",\n \"summary\": \"Actor 2 updated their Like with proof\",\n \"actor\": {\n \"id\": \"https://example.com/actors/2\",\n \"followers\": \"https://example.com/actors/2/followers\"\n },\n \"type\": \"Update\",\n \"object\": {\n \"id\": \"https://example.com/some-like\",\n \"summary\": \"Actor 2 liked a Note by actor 1, with proof\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Like\",\n \"object\": \"https://example.com/some-object\",\n \"to\": \"https://example.com/actors/1\",\n \"cc\": [\n \"https://example.com/actors/2/followers\",\n \"https://example.com/some-object/audience\",\n \"as:Public\"\n ],\n \"audience\": \"https://example.com/some-like/audience\",\n \"result\": \"https://example.com/some-proof\"\n },\n \"cc\": [\n \"https://example.com/actors/2/followers\",\n \"https://example.com/some-like/audience\",\n \"as:Public\"\n ]\n}\n If actor 1 had provided cryptographic proof, actor 2 would be able to authoritatively serve the stamp until the proof expired. Actor 1 can silently revoke the stamp by making it no longer resolve, or actively revoke the stamp by issuing a revocation activity [TODO: Update with \"undoneBy\" pointing to a Remove?]
"},{"location":"fep/0391/#a-more-complex-flow-for-stamps-involving-embedded-objects","title":"A more complex flow for stamps involving embedded objects","text":"This flow uses inReplyToProof or contextProof on the object of a Create instead of using only result on the activity itself.
Some actor creates some object that is part of some context, owned by some context moderator:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/create-some-object\",\n \"actor\": \"https://example.com/some-actor\",\n \"type\": \"Create\",\n \"object\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"attributedTo\": {\n \"id\": \"https://example.com/some-actor\",\n \"followers\": \"https://example.com/some-actor/followers\"\n },\n \"content\": \"This object is part of some context, and I can prove it was added to the context collection.\",\n \"context\": {\n \"id\": \"https://example.com/some-context\",\n \"type\": \"Collection\",\n \"attributedTo\": \"https://example.com/some-context-moderator\",\n \"audience\": \"https://example.com/some-context/audience\"\n },\n \"to\": \"https://example.com/some-context-moderator\",\n \"cc\": \"as:Public\"\n },\n \"to\": [\n \"https://example.com/some-context-moderator\",\n \"https://example.com/some-context/audience\"\n ],\n \"cc\": \"as:Public\",\n \"audience\": \"https://example.com/create-some-object/audience\"\n}\n The context moderator adds the object to the context:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/some-proof\",\n \"summary\": \"A new post was added to the context\",\n \"actor\": \"https://example.com/some-context-moderator\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-object\",\n \"target\": \"https://example.com/some-context\",\n \"attributedTo\": \"https://example.com/create-some-object\",\n \"cc\": [\n \"https://example.com/some-context/audience\",\n \"https://example.com/some-actor\",\n \"https://example.com/create-some-object/audience\",\n \"as:Public\"\n ]\n}\n The actor who created the object now can issue two updates: one for the Create activity to add a result, and one for the object of that activity to add object.contextProof:
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/update-some-object\",\n \"actor\": {\n \"id\": \"https://example.com/some-actor\",\n \"followers\": \"https://example.com/some-actor/followers\"\n },\n \"type\": \"Update\",\n \"object\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/some-actor\",\n \"content\": \"This object is part of some context, and I can prove it was added to the context collection.\",\n \"context\": \"https://example.com/some-context\",\n \"contextProof\": \"https://example.com/some-proof\",\n \"to\": \"https://example.com/some-context-moderator\",\n \"cc\": \"as:Public\"\n },\n \"to\": [\n \"https://example.com/some-actor/followers\",\n \"as:Public\"\n ]\n}\n {\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/update-create\",\n \"actor\": \"https://example.com/some-actor\",\n \"type\": \"Update\",\n \"object\": {\n \"id\": \"https://example.com/create-some-object\",\n \"actor\": \"https://example.com/some-actor\",\n \"type\": \"Create\",\n \"object\": \"https://example.com/some-object\",\n \"to\": \"https://example.com/some-context-moderator\",\n \"cc\": \"as:Public\",\n \"result\": \"https://example.com/some-proof\"\n }\n}\n"},{"location":"fep/0391/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/0391/contextProof/","title":"contextProof https://w3id.org/fep/0391/contextProof","text":"Provides proof that some object was added to some context collection
Domain : Object with context set to a Collection that has attributedTo set
Range : Add
"},{"location":"fep/0391/contextProof/#example","title":"Example","text":"{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/create-some-object\",\n \"actor\": \"https://example.com/some-actor\",\n \"type\": \"Create\",\n \"object\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/some-actor\",\n \"content\": \"This object is part of some context, and I can prove it was added to the context collection.\",\n \"context\": {\n \"id\": \"https://example.com/some-context\",\n \"type\": \"Collection\",\n \"attributedTo\": \"https://example.com/some-context-moderator\"\n },\n \"contextProof\": \"https://example.com/some-proof\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/some-context-moderator\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-comment\",\n \"target\": \"https://example.com/some-context\",\n \"attributedTo\": \"https://example.com/create-some-object\"\n }\n}\n"},{"location":"fep/0391/inReplyToProof/","title":"inReplyToProof https://w3id.org/fep/0391/inReplyToProof","text":"Provides proof that some object was added to the replies collection of some inReplyTo object
Domain : Object with inReplyTo set to an Object that has a replies collection
Range : Add
"},{"location":"fep/0391/inReplyToProof/#example","title":"Example","text":"{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/create-some-reply\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Create\",\n \"object\": {\n \"id\": \"https://example.com/some-reply\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/actors/2\",\n \"content\": \"This is a reply, and I can prove it was added to the replies collection.\",\n \"inReplyTo\": {\n \"id\": \"https://example.com/some-object\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/actors/1\",\n \"content\": \"I am accepting replies to this object.\",\n \"replies\": \"https://example.com/some-object/replies\"\n },\n \"inReplyToProof\": \"https://example.com/some-proof\"\n },\n \"result\": {\n \"id\": \"https://example.com/some-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-reply\",\n \"target\": \"https://example.com/some-object/replies\",\n \"attributedTo\": \"https://example.com/create-some-reply\"\n }\n}\n"},{"location":"fep/0391/relationshipProof/","title":"relationshipProof https://w3id.org/fep/0391/relationshipProof","text":"Provides proof that the current relationship is reciprocally claimed.
Domain : Relationship
Range : Relationship | Add | Follow? | Accept Follow?
Status : Experimental
Example 1:
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/some-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/1\",\n \"subject\": {\n \"id\": \"https://example.com/actors/1\",\n \"following\": \"https://example.com/actors/1/following\"\n },\n \"relationship\": \"IsFollowing\",\n \"object\": {\n \"id\": \"https://example.com/actors/2\",\n \"followers\": \"https://example.com/actors/2/followers\"\n },\n \"relationshipProof\": [\n {\n \"id\": \"https://example.com/not-enough-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/2\",\n \"target\": \"https://example.com/actors/1/following\"\n },\n {\n \"id\": \"https://example.com/proof-by-inverse-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/2\",\n \"subject\": \"https://example.com/actors/2\",\n \"relationship\": \"IsFollowedBy\",\n \"object\": \"https://example.com/actors/1\"\n },\n {\n \"id\": \"https://example.com/proof-by-being-added-to-followers\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/1\",\n \"target\": \"https://example.com/actors/2/followers\"\n },\n {\n \"id\": \"https://example.com/proof-by-having-follow-accepted\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Accept\",\n \"object\": {\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Follow\",\n \"object\": \"https://example.com/actors/2\"\n }\n }\n ]\n}\n Example 2:
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/0391\"],\n \"id\": \"https://example.com/some-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/1\",\n \"subject\": {\n \"id\": \"https://example.com/actors/1\",\n \"followers\": \"https://example.com/actors/1/following\"\n },\n \"relationship\": \"IsFollowedBy\",\n \"object\": {\n \"id\": \"https://example.com/actors/2\",\n \"following\": \"https://example.com/actors/2/followers\"\n },\n \"relationshipProof\": [\n {\n \"id\": \"https://example.com/not-enough-proof\",\n \"actor\": \"https://example.com/actors/1\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/2\",\n \"target\": \"https://example.com/actors/1/followers\"\n },\n {\n \"id\": \"https://example.com/proof-by-inverse-relationship\",\n \"type\": \"Relationship\",\n \"attributedTo\": \"https://example.com/actors/2\",\n \"subject\": \"https://example.com/actors/2\",\n \"relationship\": \"IsFollowing\",\n \"object\": \"https://example.com/actors/1\"\n },\n {\n \"id\": \"https://example.com/proof-by-being-added-to-following\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/actors/1\",\n \"target\": \"https://example.com/actors/2/following\"\n },\n {\n \"id\": \"https://example.com/proof-by-having-follow\",\n \"actor\": \"https://example.com/actors/2\",\n \"type\": \"Follow\",\n \"object\": \"https://example.com/actors/1\"\n }\n ]\n}\n"},{"location":"fep/03c1/","title":"FEP-03c1: Actors without acct-URI","text":"Authors Status Date received Tracking issue Discussions Repository helge @helge@mymath.rocks DRAFT 2023-11-10 #205 - codeberg"},{"location":"fep/03c1/#summary","title":"Summary","text":"Most current Fediverse applications use an acct-URI as unique display name for actors. Usually, this display is done by displaying acct:user@domain.example as @user@domain.example. This FEP states that if there is no acct-URI associated with an actor, the actor should be displayed as its id. So the actor with id https://actor.example/path will be displayed as https://actor.example/path.
In addition to the example below, we wish to point out that further independence of webfinger will enable new features such as using domain names as handles.
"},{"location":"fep/03c1/#example-rss","title":"Example: RSS","text":"Consider building an application that posts new entries of an RSS feed to the Fediverse. Let's assume we are interested in the updates of the bovine python library available through RSS at https://pypi.org/rss/project/bovine/releases.xml. Then an uri for this actor might look like
https://rss.example/actor?feed=https%3A%2F%2Fpypi.org%2Frss%2Fproject%2Fbovine%2Freleases.xml\n This would enable rss.example to automatically generate the respective actor and generate an actor object like
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://rss.example/actor?feed=https%3A%2F%2Fpypi.org%2Frss%2Fproject%2Fbovine%2Freleases.xml\",\n \"type\": \"Service\",\n \"inbox\": \"https://rss.example/inbox?feed=https%3A%2F%2Fpypi.org%2Frss%2Fproject%2Fbovine%2Freleases.xml\",\n \"outbox\": \"https://rss.example/outbox?feed=https%3A%2F%2Fpypi.org%2Frss%2Fproject%2Fbovine%2Freleases.xml\",\n \"followers\": \"https://rss.example/followers?feed=https%3A%2F%2Fpypi.org%2Frss%2Fproject%2Fbovine%2Freleases.xml\",\n \"name\": \"PyPI recent updates for bovine\",\n \"url\": \"https://pypi.org/project/bovine/\",\n \"summary\": \"Recent updates to the Python Package Index for bovine\"\n}\n There is now no obvious choice for preferredUsername and thus acct-URI. If one wanted to create one, the best choice would probably be to hash the url of the feed. This unfortunately has the consequence of not generating a human readable id, but quite the opposite.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/044f/","title":"FEP-044f: Consent-respecting quote posts","text":"Authors Status Date received Tracking issue Discussions Repository Claire claire.fep-1d7d@sitedethib.comDRAFT 2025-04-03 #547 Discussions codeberg"},{"location":"fep/044f/#summary","title":"Summary","text":"This document proposes a representation of quote posts that allows verifying consent of the quoted user, through a revocable authorization mechanism, as well as a representation of the user's choice regarding whetheir their posts can be quoted and by whom.
The approval mechanism defined in this document is systematic and required for all quotes except self-quotes, but as with Follow and Accept, approval can be granted automatically depending on the user's choice.
The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].
In the remaining of this document, \u201cquoted object\u201d refers to the object being quoted, \u201coriginal author\u201d to its author, and \u201cquote post\u201d refers to the object quoting the \u201cquoted object\u201d.
"},{"location":"fep/044f/#acknowledgments","title":"Acknowledgments","text":"(This section is non-normative.)
This proposal has benefitted from significant discussions on SocialHub as well as discussions with trwnh and GoToSocial developers. In fact, the interactionPolicy vocabulary directly comes from GoToSocial's interaction policies which have since evolved along the current proposal.
A \u201cquote post\u201d is represented as an object with a quote (https://w3id.org/fep/044f#quote) attribute.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"quote\": {\n \"@id\": \"https://w3id.org/fep/044f#quote\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"type\": \"Note\",\n \"id\": \"https://example.com/users/bob/statuses/1\",\n \"attributedTo\": \"https://example.com/users/bob\",\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\",\n \"https://example.com/users/alice\"\n ],\n \"content\": \"I am quoting alice's post<span class=\\\"quote-inline\\\"><br/>RE: <a href=\\\"https://example.com/users/alice/statuses/1\\\">https://example.com/users/alice/statuses/1</a></span>\",\n \"quote\": \"https://example.com/users/alice/statuses/1\"\n}\n This example is non-normative, and the <span class=\\\"quote-inline\\\"><br/>RE: <a href=\\\"https://example.com/users/alice/statuses/1\\\">https://example.com/users/alice/statuses/1</a></span> part of the content is an example of textual fallback, but does not otherwise carry meaning. In particular, it does not influence where the embedded quote should be displayed.
(This section is non-normative.)
While this FEP introduces https://w3id.org/fep/044f#quote, there are competing definitions for the representation of quote posts: - _misskey_quote (https://misskey-hub.net/ns/#_misskey_quote) - quoteUrl (https://www.w3.org/ns/activitystreams#quoteUrl) - quoteUri (http://fedibird.com/ns#quoteUri) - FEP-e232 Object links with a https://misskey-hub.net/ns/#_misskey_quote rel value
We believe each of those to have significant drawbacks, such as re-using a namespace that has no definition for them, implying the value is an URL or URI, or using an unusual naming scheme, and none of them are linked to a control mechanism like the one defined in this FEP, hence why we introduced https://w3id.org/fep/044f#quote.
That being said, we suggest some of them as fallback for compatibility with existing fediverse software implementations.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"quoteUrl\": \"as:quoteUrl\",\n \"quoteUri\": \"http://fedibird.com/ns#quoteUri\",\n \"_misskey_quote\": \"https://misskey-hub.net/ns/#_misskey_quote\",\n \"quote\": {\n \"@id\": \"https://w3id.org/fep/044f#quote\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"type\": \"Note\",\n \"id\": \"https://example.com/users/bob/statuses/1\",\n \"attributedTo\": \"https://example.com/users/bob\",\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\",\n \"https://example.com/users/alice\"\n ],\n \"content\": \"I am quoting alice's post<span class=\\\"quote-inline\\\"><br/>RE: <a href=\\\"https://example.com/users/alice/statuses/1\\\">https://example.com/users/alice/statuses/1</a></span>\",\n \"quote\": \"https://example.com/users/alice/statuses/1\",\n \"quoteUrl\": \"https://example.com/users/alice/statuses/1\",\n \"quoteUri\": \"https://example.com/users/alice/statuses/1\",\n \"_misskey_quote\": \"https://example.com/users/alice/statuses/1\",\n \"tag\": [\n {\n \"type\": \"Link\",\n \"mediaType\": \"application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"\",\n \"rel\": \"https://misskey-hub.net/ns#_misskey_quote\",\n \"href\": \"https://example.com/users/alice/statuses/1\"\n }\n ]\n}\n"},{"location":"fep/044f/#advertising-a-quote-policy","title":"Advertising a quote policy","text":"Users may not want their posts to be quoted, or not by everyone. To allow users to express that, we re-use GoToSocial's interaction policies and define a canQuote sub-policy.
Each quotable object advertises an interactionPolicy (https://gotosocial.org/ns#interactionPolicy) with a canQuote (https://gotosocial.org/ns#canQuote) sub-policy.
A sub-policy is defined by two attributes: - automaticApproval (https://gotosocial.org/ns#automaticApproval): an array of Actor and Collection of Actor objects from whom interactions are expected to be automatically approved - manualApproval (https://gotosocial.org/ns#manualApproval): an array of Actor and Collection of Actor objects from whom interactions are subject to manual review
Interactions from actors that are neither in automaticApproval nor manualApproval are expected to never be approved.
To advertise a policy of disallowing all quotes, interactionPolicy.canQuote.automaticApproval SHOULD contain the object author's identifier as its single value. This is because an empty array is equivalent to a missing property under JSON-LD canonicalization.
automaticApproval and manualApproval SHOULD be restricted to individual actors, the special public collection https://www.w3.org/ns/activitystreams#Public, the author's followers collection, and the author's following collection.
Note that the policy is entirely advisory. It SHOULD be used to provide user interface hints such as enabling a \u201cQuote\u201d button or explaining why an object cannot be quoted, but it MUST NOT be used to verify whether a quote post is valid. See later sections for the actual verification mechanism.
"},{"location":"fep/044f/#example_1","title":"Example","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"gts\": \"https://gotosocial.org/ns#\",\n \"interactionPolicy\": {\n \"@id\": \"gts:interactionPolicy\",\n \"@type\": \"@id\"\n },\n \"canQuote\": {\n \"@id\": \"gts:canQuote\",\n \"@type\": \"@id\"\n },\n \"automaticApproval\": {\n \"@id\": \"gts:automaticApproval\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"interactionPolicy\": {\n \"canQuote\": {\n \"automaticApproval\": \"https://example.com/users/alice/followers\"\n }\n },\n \"type\": \"Note\",\n \"id\": \"https://example.com/users/alice/statuses/1\",\n \"attributedTo\": \"https://example.com/users/alice\",\n \"to\": \"https://www.w3.org/ns/activitystreams#Public\",\n \"content\": \"I allow my followers to quote this post\"\n}\n"},{"location":"fep/044f/#QuoteAuthorization","title":"Approval stamps","text":"In order to enforce a policy, we rely on approval stamps, a mechanism used to tell third-party servers that a quote is approved, regardless of the current state of the policy.
Quote approval stamps are objects of the type QuoteAuthorization (https://w3id.org/fep/044f#QuoteAuthorization), with interactingObject (https://gotosocial.org/ns#interactingObject), interactionTarget (https://gotosocial.org/ns#interactionTarget) and attributedTo attributes.
The interactingObject attribute MUST reference the accepted quote post, the interactionTarget attribute MUST reference the quoted object, and the attributedTo attribute MUST correspond to the author of the quoted object.
A QuoteAuthorization object MUST be dereferenceable by all parties allowed to see the original post, and MAY be publicly dereferenceable. It MUST NOT embed its interactingObject as to avoid possible information leaks. For the same reason, it MUST NOT embed its interactionTarget object if the server is unable to verify that the party dereferencing the object has permission to see the quoted object.
When a third-party attempts to dereference the QuoteAuthorization, the interactionTarget MAY be inlined if the third-party has permission to access the quoted object. This is so that the third-party does not have to perform a second request to access the quoted object.
QuoteAuthorization","text":"The following stamp can be used to prove that actor https://example.com/users/alice has accepted https://example.org/users/bob/statuses/1 as a quote of her post https://example.com/users/alice/statuses/1:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"QuoteAuthorization\": \"https://w3id.org/fep/044f#QuoteAuthorization\",\n \"gts\": \"https://gotosocial.org/ns#\",\n \"interactingObject\": {\n \"@id\": \"gts:interactingObject\",\n \"@type\": \"@id\"\n },\n \"interactionTarget\": {\n \"@id\": \"gts:interactionTarget\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"type\": \"QuoteAuthorization\",\n \"id\": \"https://example.com/users/alice/stamps/1\",\n \"attributedTo\": \"https://example.com/users/alice\",\n \"interactingObject\": \"https://example.org/users/bob/statuses/1\",\n \"interactionTarget\": \"https://example.com/users/alice/statuses/1\"\n}\n"},{"location":"fep/044f/#verifying-a-quoteauthorization","title":"Verifying a QuoteAuthorization","text":"To be considered valid for a particular quote post, a QuoteAuthorization MUST satisfy the following properties: - its interactingObject is the quote post under consideration - its interactionTarget property is the quoted object - its attributedTo property is the author of its interactionTarget - the authenticity of the QuoteAuthorization object can be asserted
QuoteAuthorization","text":"An approval stamp can be revoked by Deleteing the stamp.
QuoteRequest request activity","text":"The QuoteRequest (https://w3id.org/fep/044f#QuoteRequest) activity type is introduced to request approval for a quote post.
The QuoteRequest activity uses the object property to refer to the quoted object, and the instrument property to refer to the quote post.
QuoteRequest activity","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"QuoteRequest\": \"https://w3id.org/fep/044f#QuoteRequest\",\n \"quote\": {\n \"@id\": \"https://w3id.org/fep/044f#quote\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"type\": \"QuoteRequest\",\n \"id\": \"https://example.com/users/bob/statuses/1/quote\",\n \"actor\": \"https://example.com/users/bob\",\n \"object\": \"https://example.com/users/alice/statuses/1\",\n \"instrument\": {\n \"type\": \"Note\",\n \"id\": \"https://example.com/users/bob/statuses/1\",\n \"attributedTo\": \"https://example.com/users/bob\",\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\",\n \"https://example.com/users/alice\"\n ],\n \"content\": \"I am quoting alice's post<br/>RE: https://example.com/users/alice/statuses/1\",\n \"quote\": \"https://example.com/users/alice/statuses/1\"\n }\n}\n"},{"location":"fep/044f/#receiving-accepting-or-rejecting-quote-posts","title":"Receiving, accepting or rejecting quote posts","text":"When receiving a QuoteRequest activity, the original author decides (either manually or automatically) whether the quote is acceptable. Software that automatically accepts quotes on the author's behalf should notify the author of such quotes according to their notification settings.
The receiving end MAY inspect the instrument of the QuoteRequest itself to decide whether it is acceptable.
If the quote post is considered acceptable, the original author MUST reply with an Accept activity with the QuoteRequest activity as its object, and a QuoteAuthorization as its result.
If the quote post is considered unacceptable, the authority SHOULD reply with a Reject activity with the QuoteRequest activity as its object.
Accept","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"QuoteRequest\": \"https://w3id.org/fep/044f#QuoteRequest\"\n }\n ],\n \"type\": \"Accept\",\n \"to\": \"https://example.com/users/bob\",\n \"id\": \"https://example.com/users/alice/activities/1234\",\n \"actor\": \"https://example.com/users/alice\",\n \"object\": {\n \"type\": \"QuoteRequest\",\n \"id\": \"https://example.com/users/bob/statuses/1/quote\",\n \"actor\": \"https://example.com/users/bob\",\n \"object\": \"https://example.com/users/alice/statuses/1\",\n \"instrument\": \"https://example.org/users/bob/statuses/1\"\n },\n \"result\": \"https://example.com/users/alice/stamps/1\"\n}\n"},{"location":"fep/044f/#example-reject","title":"Example Reject","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"Quote\": \"https://w3id.org/fep/044f#QuoteRequest\"\n }\n ],\n \"type\": \"Reject\",\n \"to\": \"https://example.com/users/bob\",\n \"id\": \"https://example.com/users/alice/activities/1234\",\n \"actor\": \"https://example.com/users/alice\",\n \"object\": {\n \"type\": \"QuoteRequest\",\n \"id\": \"https://example.com/users/bob/statuses/1/quote\",\n \"actor\": \"https://example.com/users/bob\",\n \"object\": \"https://example.com/users/alice/statuses/1\",\n \"instrument\": \"https://example.org/users/bob/statuses/1\"\n }\n}\n"},{"location":"fep/044f/#quoteAuthorization","title":"Requesting, obtaining and validating approval","text":"In order to get approval, the quote post author MUST send a QuoteRequest (https://w3id.org/fep/044f#QuoteRequest) activity to the author of the quoted object, with the quoted object as its object property and the quote post as its instrument.
The quote post SHOULD be inlined in the instrument property and, if not, it SHOULD dereferenceable by the recipient at this point, as the author of the quoted object may want to inspect it to decide whether to accept the quote.
The quote post author MAY wait until they receive an Accept or Reject activity before sending the post's Create activity to its intended audience. Doing so is possible for ActivityPub servers that implement the current proposal, and avoids having to issue an Update soon afterwards the Create for the quote post. It is however not possible to implement for ActivityPub clients, which will likely need to issue a Create before the QuoteRequest activity.
If the author of the quote post receives a Reject activity from the quoted object's author to their QuoteRequest activity, they MUST consider the quote post to be explicitly rejected.
If the implementation waits for the Accept before issuing a Create, this MAY translate as the inability to publish the quote post.
Otherwise, it MAY translate as a Delete to outright remove the quote post, or an Update to remove the quote part from the post.
If the author of the quote receives an Accept activity, they MUST add a reference to its result in the quoteAuthorization (https://w3id.org/fep/044f#quoteAuthorization) property.
Depending on whether they already sent a Create activity to the quote post's intended audience, they SHOULD send a Create activity or an Update activity with the updated quoteAuthorization property.
Note object","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"quote\": \"https://w3id.org/fep/044f#quote\",\n \"quoteAuthorization\": {\n \"@id\": \"https://w3id.org/fep/044f#quoteAuthorization\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"type\": \"Note\",\n \"id\": \"https://example.com/users/bob/statuses/1\",\n \"attributedTo\": \"https://example.com/users/bob\",\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\",\n \"https://example.com/users/alice\"\n ],\n \"content\": \"I am quoting alice's post<br/>RE: https://example.com/users/alice/statuses/1\",\n \"quote\": \"https://example.com/users/alice/statuses/1\",\n \"quoteAuthorization\": \"https://example.com/users/alice/stamps/1\"\n}\n"},{"location":"fep/044f/#verifying-third-party-quote-posts","title":"Verifying third-party quote posts","text":"When processing a quote post from a remote actor, a recipient MUST consider them unapproved unless any of those conditions apply: - the author of the quote post and that of the original post are the same (same attributedTo) - quoteAuthorization exists, can be dereferenced and is a valid QuoteAuthorization activity for this object
The original author may want to perform /a posteriori/ moderation of the quote posts, or block a quote poster in particular.
To do this, the original actor MUST Delete the QuoteAuthorization. They SHOULD send the Delete activity to the quote post's author and any recipient it has reasons to think has accessed the quote post.
The original author MUST NOT embed the object nor the target of the QuoteAuthorization, so as to avoid potential information leakage.
Upon receiving a Delete activity for a previously-verified QuoteAuthorization, third-parties MUST check that the Delete is valid and MUST subsequently consider the quote post unapproved.
Additionally, if the recipient owns the quote post, it MUST forward the Delete to the audience of the quote post.
Because getting revocation properly forwarded depends on the good will of the revoked post's author, it may be necessary to have other means of checking whether an approval has been revoked.
For this reason, recipients SHOULD re-check the quoteAuthorization document when an already-known quote post is accessed for the first time in a given period of time.
This proposal has been made with great care to not require new server behavior, allowing ActivityPub clients to implement this proposal without requiring generic ActivityPub server software to implement additional logic.
In particular, this is the reason the approval stamp is a separate object rather than the Accept itself. Indeed, nothing in the ActivityPub specification would cause a Reject or Undo activity to invalidate the Accept activity itself, which means it would not be suitable as an approval stamp. While ActivityPub does not technically forbid Accept activities to be the target of a Delete activity, we have found no precedent for that, and we anticipate that deleting activities might not be correctly handled across the fediverse. For this reason, we opted to use a separate object that can be directly managed by an ActivityPub client, for instance by issuing a Create activity ahead of sending the Accept activity, and that can be deleted with the usual mechanism.
An alternative approach we considered is using a dedicated endpoint to check for approval of a quote. This would effectively allow externalizing approval verification to a separate mechanism, but while this would not require new server behavior, this would still require a new server component to be specified, which is why we opted for the mechanism described in this specification instead.
"},{"location":"fep/044f/#backward-compatibility-considerations","title":"Backward compatibility considerations","text":"(This section is non-normative.)
To clients that do not implement this FEP, quote posts are seen as regular posts with no explicit relation with the quoted post. While this is preferable to having the quoted post relayed without the comment, this is still lacking significant semantic context.
Therefore, quote posts should be authored in such a way that their contents include a reference to the quoted post, e.g. by adding <span class=\\\"quote-inline\\\"><br/>RE: <a href=\\\"https://example.com/users/alice/statuses/1\\\">https://example.com/users/alice/statuses/1</a></span>.
Using a special class like quote-inline can be useful to hide redundant information information when the post is detected to have an attached quote.
(This section is non-normative.)
Servers that do not implementing the current FEP will still be able to quote the post without providing any dogpiling-reducing friction. There is unfortunately nothing we can do about that. However, servers which do implement the current FEP should refuse displaying those quotes, so implementing the control mechanisms of the current FEP remains worthwhile.
The current FEP does not specify behavior around the visibility of quoted posts, but implementations should pay special attention to not inadvertently expose the contents of a quoted post to users who are not allowed to see it. In particular, implementations should ensure that any restriction based on user blocks, or follow relationships are still respected. Likewise, implementations should pay attention so that any discoverability, searchability or privacy setting, such as FEP-5feb, are still respected. To reduce risk of accidental exposure through third-party servers with insufficient visibility checks, it is recommended to not allow quoting posts with a wider allowed audience than that of the quoted post.
Effectively revoking authorized quote posts relies on the participation of the quote poster's server to effectively reach the audience of the quote post. This means that an ill-intentioned server which obtained an authorization could deliberately refuse to forward the revocation. Still, the ability to revoke a quote post remains useful between well-intentioned servers, and opportunistic re-verification of quote approvals should also help with discovering that a quote authorization has been revoked, despite the potential lack of forwarding.
By not adding a hash or copy of the reply in the QuoteAuthorization object, malicious actors could exploit this in a split horizon setting, sending different versions of the same activity to different actors. This is, however, already a concern in pretty much all contexts in ActivityPub, and enshrining that information in the QuoteAuthorization object would have many drawbacks: - significantly more complex implementation - inability to change the JSON-LD representation after the fact - possibly leaking private information if the QuoteAuthorization object is publicly dereferenceable
QuoteAuthorization in response to a QuoteRequest, it does not currently verify quotes nor request stampsas:Public, the author's followers collections, or nobody)CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/0499/","title":"FEP-0499: Delivering to multiple inboxes with a multibox endpoint","text":"Authors Status Date received Tracking issue Discussions Repository aDRAFT 2024-09-30 #402 Discussions codeberg"},{"location":"fep/0499/#summary","title":"Summary","text":"This FEP introduces a server-wide endpoint for delivering activities to multiple inboxes. sharedInbox currently allows for doing this, but it requires the remote server to know how to deliver the activity based on its addressing properties. However, the remote server might not know how to deliver the activity to private recipients, or recipients within a collection. The multibox endpoint removes this knowledge requirement from the receiving server and instead makes the sending server responsible for marking inboxes to explicitly deliver to.
"},{"location":"fep/0499/#motivation","title":"Motivation","text":"(This section is non-normative.)
sharedInbox allows servers to reduce network traffic when delivering Public activities to multiple recipients, but it doesn't work when delivering to collections of actors, or when delivering to private audiences using bto or bcc. If an activity is addressed to a collection and that activity is delivered to a remote sharedInbox endpoint, then this introduces a requirement for the remote server to know the contents of the collection (or at least the local subset of its contents). In the common case of delivering activities addressing followers collections to a sharedInbox endpoint, then the remote server must first recognize that this collection id is specifically a followers collection, and then guess which local actors are following that activity's actor. This is prone to issues and can also have disastrous outcomes when follower state drifts out of sync.
We can eliminate the dependency on shared follower state and allow for delivery of non-Public activities by introducing a new endpoint that likewise allows delivering to multiple inboxes, but without requiring the remote server to know anything. This enables addressing arbitrary collections as well as the use of bto and bcc more efficiently.
(This section is non-normative.)
There are various advantages described in the original multibox proposal:
Shared Inbox provides the ability for server to server communication traffic to be reduced from R requests, where R is the number of recipients, to a single HTTP request. This is a desirable property as it reduces the amount of HTTP round trips for both the sender and receiver. Unfortunately the design of Shared Inboxes as described in the ActivityPub specification makes it very easy for a spammer to abuse the system by not requiring explicit delivery recipients. We propose an alternative to Shared Inbox called MultiBox that keeps the desirable properties of Shared Inbox while protecting against scenarios in which the sender uses Shared Inbox to \"spam\" a server.
Like Shared Inbox, MultiBox consists of a single HTTP endpoint for multiple Actors. Unlike Shared Inbox, in a MultiBox request, each recipient is explicitly listed by Inbox, requiring both the knowledge of the Actor and a corresponding Inbox for that actor. This information is transmitted through the use of an HTTP header Audience where each Inbox is listed using comma separated values.
This has two advantages over Shared Inbox. Used on its own, it eliminates the vulnerability mentioned previously whereby recipients to a message do not need to be listed. If this proposal is adopted alongside the Object-Capabilities Based Inbox proposal (4.5), the advantages multiply as we also gain the ability to appropriately filter incoming messages according to the criteria set out by the specific Inboxes, as well as letting us know the origin of each Inbox.
For the sender, the additional computing resources required to send a MultiBox request are minimal, but doing so would make mass-messages expensive for senders wishing to abuse the system.
One open question on this proposal is that if we use the HTTP header Audience to store the list of recipients, this may result in a limitation. HTTP header sizes are not explicitly capped at the protocol level but implementations often cap them at different lengths- 4Kb for the Nginx web server or 8Kb for Apache.
This would limit the number of per message recipients, though this limitation would rarely be reached. An alternative to this proposal would be a new MultiBox object encapsulating the Audience field and the ~Activity.
"},{"location":"fep/0499/#proposal","title":"Proposal","text":"(This section is non-normative.)
This FEP uses the \"alternative\" approach of putting inboxes in the body of the POST request, instead of using an HTTP header. Whereas headers are limited to possibly as low as 4Kb by default, the limit on POST request bodies is usually much higher; Nginx by default uses a 1MB limit for POST requests. This is the difference between 4000 characters versus 1 million characters.
"},{"location":"fep/0499/#specification","title":"Specification","text":"Actors MAY have a multibox endpoint which is available for efficient delivery of activities to multiple inboxes on the same domain.
A server MAY reduce the number of delivery requests by identifying all recipients which share the same multibox who would otherwise be delivered to individually, and deliver an activity of the following form to their shared multibox endpoint:
Add.object MUST be the activity being delivered.target MUST be all inboxes being delivered to.Upon receipt of such an activity, the receiving server MUST add the activity in object into all local inboxes specified in target, although it MAY filter delivery to certain inboxes according to implementation-specific rules (for example, spam filtering).
(This section is non-normative.)
Discovering the multibox endpoint:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/0499\"\n ],\n \"id\": \"https://remote.example/actors/af4c8205cd81\",\n \"type\": \"Person\",\n \"name\": \"Alice P. Hacker\",\n \"inbox\": \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"endpoints\": {\n \"multibox\": \"https://remote.example/multibox\"\n }\n}\n Alternatively, without a context declaration:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://remote.example/actors/af4c8205cd81\",\n \"type\": \"Person\",\n \"name\": \"Alice P. Hacker\",\n \"inbox\": \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"endpoints\": {\n \"https://w3id.org/fep/0499/multibox\": {\"id\": \"https://remote.example/multibox\"}\n }\n}\n Delivering to the multibox endpoint:
POST /multibox HTTP/1.1\nHost: remote.example\nContent-Type: application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-activity\",\n \"target\": [\n \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"https://remote.example/inboxes/d21f509146e5\",\n \"https://remote.example/inboxes/68a7453f79e4\",\n \"https://remote.example/inboxes/655216a0be07\",\n \"https://remote.example/inboxes/84907eff485d\",\n ]\n}\n For transient activities or for cases not requiring a fetch (e.g. no access control), you can embed the activity inline:
POST /multibox HTTP/1.1\nHost: remote.example\nContent-Type: application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Add\",\n \"object\": {\n \"@context\": \"https://context.example\"\n \"actor\": \"https://example.com/some-actor\",\n \"type\": \"InGameNotification\",\n \"content\": \"The payload is nearing the checkpoint!\",\n \"generator\": \"https://game.example\"\n },\n \"target\": [\n \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"https://remote.example/inboxes/d21f509146e5\",\n \"https://remote.example/inboxes/68a7453f79e4\",\n \"https://remote.example/inboxes/655216a0be07\",\n \"https://remote.example/inboxes/84907eff485d\",\n ]\n}\n"},{"location":"fep/0499/#terms-defined","title":"Terms defined","text":"multibox URI https://w3id.org/fep/0499/multibox Label multibox endpoint Comment An optional endpoint used for wide delivery of activities to multiple inboxes. POST an Add activity where the object is at least one activity to be delivered, and the target is at least one inbox to deliver to. Domain A map of endpoints Range The endpoint (@id) Required No Functional Yes Is defined by FEP-0499 \n\n{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/0499\"\n ],\n \"id\": \"https://remote.example/actors/af4c8205cd81\",\n \"type\": \"Person\",\n \"name\": \"Alice P. Hacker\",\n \"inbox\": \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"endpoints\": {\n \"multibox\": \"https://remote.example/multibox\"\n }\n}\n\n \n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://remote.example/actors/af4c8205cd81\",\n \"type\": \"Person\",\n \"name\": \"Alice P. Hacker\",\n \"inbox\": \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"endpoints\": {\n \"https://w3id.org/fep/0499/multibox\": {\"id\": \"https://remote.example/multibox\"}\n }\n}\n\n \n\nPOST /multibox HTTP/1.1\nHost: remote.example\nContent-Type: application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-activity\",\n \"target\": [\n \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"https://remote.example/inboxes/d21f509146e5\",\n \"https://remote.example/inboxes/68a7453f79e4\",\n \"https://remote.example/inboxes/655216a0be07\",\n \"https://remote.example/inboxes/84907eff485d\",\n ]\n}\n\n"},{"location":"fep/0499/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/0499/multibox/","title":"multibox","text":"URI : https://w3id.org/fep/0499/multibox
Label : multibox endpoint
Comment : An optional endpoint used for wide delivery of activities to multiple inboxes. POST an Add activity where the object is at least one activity to be delivered, and the target is at least one inbox to deliver to.
Domain : A map of endpoints
Range : The endpoint (@id)
Required : No
Functional : Yes
Is defined by : FEP-0499
"},{"location":"fep/0499/multibox/#examples","title":"Examples","text":"Discovering a multibox endpoint with additional context
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/0499\"\n ],\n \"id\": \"https://remote.example/actors/af4c8205cd81\",\n \"type\": \"Person\",\n \"name\": \"Alice P. Hacker\",\n \"inbox\": \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"endpoints\": {\n \"multibox\": \"https://remote.example/multibox\"\n }\n}\n Discovering a multibox endpoint without additional context
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://remote.example/actors/af4c8205cd81\",\n \"type\": \"Person\",\n \"name\": \"Alice P. Hacker\",\n \"inbox\": \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"endpoints\": {\n \"https://w3id.org/fep/0499/multibox\": {\"id\": \"https://remote.example/multibox\"}\n }\n}\n Delivering to a multibox endpoint
POST /multibox HTTP/1.1\nHost: remote.example\nContent-Type: application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Add\",\n \"object\": \"https://example.com/some-activity\",\n \"target\": [\n \"https://remote.example/inboxes/fbb433c8e6c4\",\n \"https://remote.example/inboxes/d21f509146e5\",\n \"https://remote.example/inboxes/68a7453f79e4\",\n \"https://remote.example/inboxes/655216a0be07\",\n \"https://remote.example/inboxes/84907eff485d\",\n ]\n}\n"},{"location":"fep/07d7/","title":"FEP-07d7: A Custom URL Scheme and Web-Based Protocol Handlers for Linking to ActivityPub Resources","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Jennifer Moore contact@jenniferplusplus.com WITHDRAWN 2023-09-22 2025-04-22 #171 - codeberg This specification addresses sometimes difficult interactions with ActivityPub resources hosted on remote servers. It defines a custom URL scheme which can be used by custom web-based protocol handlers to route hyperlinks to those resources to the user's preferred server. It additionally advises when ActivityPub servers can include these links in HTML views they generate, and how clients and servers can implement those web-based protocol handlers.
"},{"location":"fep/07d7/#motivation","title":"Motivation","text":"When a person follows a link to some ActivityPub powered site, the browser will navigate to the resource on that site. This is often not what the person would have prefered when they have an account on a different ActivityPub aware site. In order to interact with the linked resource from their own account, the person must separately open their home server and search for the resource. This is an awkward and sometimes confusing process, especially for people who are not already familiar with the idosyncracies of navigating a federated social network.
Web-based protocol handling is a feature of modern web browsers. This allows a site to register with the user's browser as a handler for custom URI schemes. The browser will send links using that scheme to the registerd handler. This way, it's possible to direct links to the user's preferred server or application. The handler can retrieve the resource and provide familiar and appropriate presentation. Native ActivityPub clients can also take advantage of these URIs to provide similar handling.
"},{"location":"fep/07d7/#context","title":"Context","text":"Similar proposals such as Fedilinks and Mastodon Issue 19679 have been made, and were even briefly implemented in Mastodon. There seems to be some concensus that custom protocol handlers have the technical capability to solve the problem of difficult interactions with cross-instance objects. There is also ample prior art for this approach. mailto: and tel: are common examples from web standards. Zoom uses a customzoommtg: protocol to launch their desktop app, and Apple uses itms: to launch iTunes.
It seems the main impediment to early attempts was a perception of poor UX and limited adoption. This seems to be mostly a chicken-and-egg problem. That will always be a problem, until it's not. In this case, it seems it would be helpful to let standards drive implementation, as the opposite hasn't ocurred. This proposal also recommends behavior that is compatible with gradual adoption.
Some similar proposals have also disussed emphasizing outcome, rather than content. Encapsulating action is an understandable desire, and this proposal attempts to facilitate that. But the protocol aspect is firmly focussed on interaction with ActivityPub objects, without making any assumptions about the design or capabilities of current or future ActivityPub services. The hope is that this gives the resulting implementations better longevity. Documents can outlive software, and people should still be able to interact with them in useful ways. This also keeps the proposal scoped just to ActivityPub concerns, without imposing on other standards.
"},{"location":"fep/07d7/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this specification are to be interpreted as described in RFC-2119.
"},{"location":"fep/07d7/#1-definitions","title":"1. Definitions","text":"This proposal refers to ActivityPub objects as viewed and represented in multiple ways from multiple servers. For clarity, these are the terms that will be used to describe those scenarios.
Origin server means the server that has authority for the ActivityPub object. This is the server that hosts and controls access to the object, and will recieve requests for the URI used as the object's ID.
Handler is any software that handles the web+activitypub: scheme and protocol. Typically either an ActivityPub server where the person has an account, or a client application they have installed on their device.
ActivityPub object means the JSON-LD document representing an ActivityPub Object, as described in the ActivityPub and ActivityStreams[ActivityStreamsRef] specifications.
HTML representation means an HTML or other document rendered to display the ActivityPub object for human use.
A client is any software that provides a human-friendly presentation of ActivityPub objects, or can interact with an ActivityPub server. For example, this could be a server's web UI or a native mobile app. This software does not necessarily utilize the C2S profile of the ActivityPub spec.
"},{"location":"fep/07d7/#2-uri-scheme","title":"2. URI Scheme","text":"When creating hyperlinks to ActivityPub resources, individuals and applications SHOULD include a link using the custom web+activitypub: scheme. This scheme can be handled by web-based or native handlers registered with browsers by end-users. Because there's no guarantee that a given browser will have any registered handler for this scheme, these links SHOULD NOT be used in place of a link that refers to the resource by ID or an alternative HTML representation of it. Links using the web+activitypub: scheme SHOULD be used as an addition to those more canonical links.
The address provided using the web+activitypub: scheme SHOULD be the same as the referenced ActivityPub object's ID. The address MAY instead be for an alternative human-readable address, or for an HTML representation of the object, as normal for the origin server. Applications using or generating these links should be aware that not all clients will be able to dereference those alternative or human readable addresses. The most interoperable choice is to use the object's canonical ActivityPub ID.
The linked address MUST replace the scheme used with web+activitypub:. That means the link MUST NOT simply prepend web+activitypub: onto a preexisting https: or other scheme, as this would violate the generic URI syntax specified by RFC-3986
A web+activitypub: link MAY encapsulate an intent for an activity to be performed on or with the linked object. For example, a pre-assembled link could be used to directly Follow an actor or to Announce an object. An encapsulated intent MUST be represented by appending a query string to the target URI. It's possible that the target URI already includes a query string. In this case, to encapsulate an intent, the intent MUST append a new query parameter to the existing query string. A web+activitypub: link MUST NOT encapsulate more than one intent.
Including an encapsulated intent is OPTIONAL. Responding to the intent is also OPTIONAL. The creator of the intent should keep in mind that the handler MAY support intents, but does not have to. Also keep in mind that the handler MUST provide a mechanism for the user to confirm or decline to perform the intended action. The use or purpose of the web+activitypub: link MUST NOT require that the intended activity is actually performed.
The format of the query parameter to encapsulate an intent is intent=<activity> where <activity> is any of the following activities described by the Activity Streams vocabulary. The activity string SHOULD be all lowercase. It's permissible to use any casing. But, there are many systems in the wild which might intercept or preprocess the URIs in a web+activitypub: link, and not all of those will respect the original casing.
The web+activitypub: link SHOULD NOT encapsulate an intent for any activity not listed above. In addition, the link MUST NOT encapsulate an intent for any of the following activities. Implementers should exercise discretion regarding activities added as extensions to the ActivityPub specification. You should err on the side taking no action where there is doubt about the safety or security of doing so.
For security and privacy, web+activitypub: links MUST NOT include a username or password component. They also SHOULD NOT be relative URIs.
Below are some examples of web+activitypub: hyperlinks that reflect some expected uses.
One-click check in
A link to enable one-click check in to pick up a shopping order.
<a hfref=\"web+activitypub:shopping.example/pickup/12345?intent=arrive\">Check in to pick up your order</a>
One-click follow
A link to enable one-click following from another website. The handler must not perform the follow activity unless the user confirms it.
<a href=\"web+activitypub:uss-enterprise.example/user/picard?intent=follow>Follow me on the fediverse</a>
Link with no intent
A link to view a blog post published as an ActivityPub Article
<a href=\"web+activitypub:my-blog.example/article/write-your-first-fep>Read the article on your home instance</a>
ActivityPub applications that can retrieve remote objects and generate an HTML display MAY also implement a web-based protocol handler for the web+activitypub: URI scheme. Applications MAY use other available registration mechanisms, such as Android inents. The handler MUST attempt to load the specified URI as an application/ld+json document. If the origin does not respond with an ActivityPub object, the handler SHOULD fallback to other methods it uses to locate ActivityPub objects, such as webfinger.
When resolving the given URI, the handler MUST ignore any provided username or password component, and it MUST exclude those components from the requests it makes to retrieve the object. If the given URI includes a query string with an intent parameter, the handler MUST exclude the intent parameter from the requests made to retrieve the object.
The handler MUST NOT attempt to retrieve objects from a relative URI. There's likely no good reason for a web+activitypub: link to target a relative URI, and there is some risk it could be used to facilitate scams or phishing attempts by making it appear some third party has access to data they shouldn't.
The handler MUST perform the same sanitization or other safe handling of untrusted URIs as it normally would. For example, there's likely no good reason for a production system to try to load resources from localhost, or using an ip address, rather than a hostname. Doing so could also facilitate phishing or scams.
When retreiving resources identified by a web+activitypub: scheme, the handler SHOULD assume the origin uses HTTPS. The handler MAY attempt to use other schemes, such as did:. The handler MAY make this determination using any heuristic or algorithm the developers choose.
Other than the above considerations, the handler MUST attempt to resolve the URI exactly as given.
"},{"location":"fep/07d7/#32-displaying-hyperlinked-objects","title":"3.2 Displaying hyperlinked objects","text":"After successfully retrieving the referenced object, the handler SHOULD produce an HTML representation of the object and present it for the user. The handler MUST escape and sanitize the content of the object before displaying it, to prevent cross-site scripting attacks.
The handler SHOULD display the object in the normal way for the application, and in the normal context. For example, if the application normally displays replies alongside an object, it SHOULD do so in this case as well.
The handler SHOULD enable the user to interact with the object normally, such as to generate Like or Announce activities referring to it.
When the provided web+activitypub: URI includes an encapsulated intent, the handler MAY prompt the user to perform that intended activity. The handler MUST NOT perform that activity without explicit confirmation from the user. The handler MUST NOT perform or offer to perform the intended activity if it is one of the ones listed below. These activities present an elevated risk of disrupting the user's social connections or enlisting them in malicious activities. The handler MAY perform other activities, but SHOULD do so with care. web+activitypub: links are usually created by third parties. Be mindful that there is some risk of exposing sensitive information or facilitating harmful behavior when third parties are allowed to direct other people's actions.
The handler MUST NOT perform any of these activities in response to an ecapsulated intent:
ActivityPub servers MAY offer users an option to register with their browser as a web+activitypub: handler. Servers SHOULD NOT do this unless the user has authenticated with the server. The server SHOULD also offer a similar option to de-register as a handler. Servers SHOULD NOT attempt to register as a handler unless initiated by the user in some way. Servers MUST NOT attempt to automatically register as a handler again after a user has declined to allow it.
Clients MAY offer users an option to register as handlers for web+activitypub: links. They SHOULD use whatever mechanism is appropriate to the client to do so. For instance, Android apps would likely use the intent system, and Windows apps could set the appropriate registry flags. Clients SHOULD provide a mechanism to de-register as a handler, where possible. Clients MAY defer this capability to the operating system as appropriate.
If a client will retrieve the linked object itself, the client MUST adhere to the same specifications from section 3.1 and 3.2 as any other application would.
"},{"location":"fep/07d7/#4-safety-and-security","title":"4. Safety and Security","text":"Because there is no way to know or control which application will ultimately handle any web+activitypub: link, creators SHOULD NOT include private or sensitive information in the link. Creators MUST NOT include any type of authenticating token or credential in the link. And handling applications themselves MUST apply all the normal and appropriate access controls when retrieving and displaying resources linked this way.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/0837/","title":"FEP-0837: Federated Marketplace","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2023-08-17 #147 Discussions codeberg"},{"location":"fep/0837/#summary","title":"Summary","text":"This document describes a minimal implementation of a federated marketplace based on ActivityPub protocol and Valueflows vocabulary. In such marketplace actors can publish offers and requests, respond to offers and requests published by other actors, enter into agreements and exchange information necessary to complete these agreements.
"},{"location":"fep/0837/#history","title":"History","text":"Extension of ActivityPub protocol with Valueflows vocabulary was initially proposed by Lynn Foster in FEP-d767.
"},{"location":"fep/0837/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/0837/#overview","title":"Overview","text":"sequenceDiagram\n actor Alice\n actor Bob\n Note right of Alice: Alice publishes a Proposal\n Bob ->> Alice: Bob sends Offer(Agreement) activity\n Alice ->> Bob: Alice sends Accept(Agreement) activity\n Note over Alice, Bob: Alice and Bob complete the transaction\n Alice ->> Bob: Alice sends confirmation activity"},{"location":"fep/0837/#proposals","title":"Proposals","text":"Valueflows defines proposals as published requests or offers, sometimes with what is expected in return.
The representation of a proposal is a JSON document with the following properties:
id (REQUIRED): the proposal's unique global identifier.type (REQUIRED): the type of the object SHOULD be Proposal. If interoperability with other ActivityPub services is desirable, implementers MAY use object types from Activity Vocabulary, such as Note.purpose (REQUIRED): the type of proposal. Possible values are offer and request.attributedTo (REQUIRED): the identifier of an actor who published the proposal.name (RECOMMENDED): the title of the proposal.content (OPTIONAL): the description of the proposal. The type of content SHOULD be text/html.published (RECOMMENDED): the date and time at which the proposal was published.location (OPTIONAL): indicates a physical location associated with the proposal. The representation of location MUST conform to the recommendations of Activity Vocabulary document, section 5.3 Representing Places.publishes (REQUIRED): the primary intent of this proposal (see below).reciprocal (OPTIONAL): the reciprocal intent of this proposal (see below).unitBased (OPTIONAL): indicates whether the quantities expressed in the proposal can be multiplied or not. The default is false.to (REQUIRED): the audience of the proposal.Intents are proposed economic transactions. The primary intent describes what is being offered or requested, and reciprocal intent describes what is expected or offered in return. Some examples:
The representation of an intent is a JSON document with the following properties:
id (REQUIRED): the unique global identifier of the intent. Implementations SHOULD use URI fragments to identify intents associated with a given proposal. The RECOMMENDED fragment identifiers for primary and reciprocal intents are primary and reciprocal.type (REQUIRED): the type of the object MUST be Intent.action (REQUIRED): the type of economic transaction. The value of this property SHOULD be either deliverService or transfer.resourceConformsTo (RECOMMENDED): the type of an economic resource. Could be any URI.resourceQuantity (REQUIRED): the amount and unit of the economic resource. This is an object with two properties:hasUnit (REQUIRED): name of the unit, according to Ontology of units of Measure classification. The RECOMMENDED unit for countable items is one.hasNumericalValue (OPTIONAL): the amount of the resource. If not specified, arbitrary amounts can be used when responding to the proposal.availableQuantity (OPTIONAL): the quantity of the offered resource currently available.minimumQuantity (OPTIONAL): the minimum possible quantity of the resource.Minimal example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Proposal\": \"vf:Proposal\",\n \"Intent\": \"vf:Intent\",\n \"action\": \"vf:action\",\n \"purpose\": \"vf:purpose\",\n \"unitBased\": \"vf:unitBased\",\n \"publishes\": \"vf:publishes\",\n \"resourceQuantity\": \"vf:resourceQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\"\n }\n ],\n \"type\": \"Proposal\",\n \"id\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930\",\n \"purpose\": \"offer\",\n \"attributedTo\": \"https://market.example/users/alice\",\n \"publishes\": {\n \"type\": \"Intent\",\n \"id\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#primary\",\n \"action\": \"transfer\",\n \"resourceQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"1\"\n }\n },\n \"to\": \"https://www.w3.org/ns/activitystreams#Public\"\n}\n Full example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Proposal\": \"vf:Proposal\",\n \"Intent\": \"vf:Intent\",\n \"action\": \"vf:action\",\n \"purpose\": \"vf:purpose\",\n \"unitBased\": \"vf:unitBased\",\n \"publishes\": \"vf:publishes\",\n \"reciprocal\": \"vf:reciprocal\",\n \"resourceConformsTo\": \"vf:resourceConformsTo\",\n \"resourceQuantity\": \"vf:resourceQuantity\",\n \"availableQuantity\": \"vf:availableQuantity\",\n \"minimumQuantity\": \"vf:minimumQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\"\n }\n ],\n \"type\": \"Proposal\",\n \"id\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930\",\n \"purpose\": \"offer\",\n \"attributedTo\": \"https://market.example/users/alice\",\n \"name\": \"Offering used bike\",\n \"content\": \"Blue one-speed bike, 15 years old, some rust\",\n \"published\": \"2023-06-18T19:22:03.918737Z\",\n \"location\": {\n \"type\": \"Place\",\n \"longitude\": -71.0,\n \"latitude\": 25.0\n },\n \"publishes\": {\n \"type\": \"Intent\",\n \"id\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#primary\",\n \"action\": \"transfer\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q11442\",\n \"resourceQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"1\"\n },\n \"availableQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"1\"\n },\n \"minimumQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"1\"\n }\n },\n \"reciprocal\": {\n \"type\": \"Intent\",\n \"id\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#reciprocal\",\n \"action\": \"transfer\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q4917\",\n \"resourceQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"30\"\n }\n },\n \"unitBased\": false,\n \"to\": \"https://www.w3.org/ns/activitystreams#Public\"\n}\n"},{"location":"fep/0837/#publishing-a-proposal","title":"Publishing a proposal","text":"Proposals can be linked to actors (if actor provides a service) or to other objects (if they represent economic resources) using FEP-0ea0 payment links. Proposals can also be added to public collections, or be delivered to actor's followers using Create activity, or announced by group actors.
If FEP-0ea0 payment link is used, its href attribute MUST contain the proposal ID and its rel array MUST contain the string https://w3id.org/valueflows/ont/vf#Proposal. The value of mediaType attribute SHOULD be application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\".
Example of a proposal attached to an actor via payment link:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Person\",\n \"id\": \"https://market.example/users/alice\",\n \"inbox\": \"https://market.example/users/alice\",\n \"outbox\": \"https://market.example/users/alice\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Buy a bike\",\n \"mediaType\": \"application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"\",\n \"href\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930\",\n \"rel\": [\"payment\", \"https://w3id.org/valueflows/ont/vf#Proposal\"]\n }\n ]\n}\n Consuming implementations which don't have marketplace features MAY display proposals similarly to Note objects.
An interested party responds to a proposal and then parties start negotiating to reach an agreement.
To respond to a proposal, an interested party MUST send an Agreement object wrapped in Offer activity to the actor indicated by the attributedTo property of the proposal. The proposing party MUST either commit to the action described in the proposal or send a rejection.
In the first case, the proposer finalizes the agreement and sends Accept(Offer) activity back to the interested party.
In the second case, the proposer sends Reject(Offer) activity. The interested party MAY send Offer(Agreement) activities many times until agreement is reached.
The representation of an agreement is a JSON document with the following properties:
id (OPTIONAL): the unique global identifier of the agreement. This property is REQUIRED for finalized agreements.type (REQUIRED): the type of the object MUST be Agreement.attributedTo (OPTIONAL): the identifier of an actor who created the agreement.stipulates (REQUIRED): the primary commitment associated with the agreement.stipulatesReciprocal (OPTIONAL): the reciprocal commitment associated with the agreement. It is REQUIRED if the corresponding proposal has a reciprocal intent.Commitments are promised economic transactions. The representation of a commitment is a JSON document with the following properties:
id (OPTIONAL): the unique global identifier of the commitment. This property is REQUIRED for commitments in finalized agreements. Implementations SHOULD use URI fragments to identify commitments associated with a given agreement. The RECOMMENDED fragment identifiers for commitments satisfying primary and reciprocal intents of the proposal are primary and reciprocal.type (REQUIRED): the type of the object MUST be Commitment.satisfies (REQUIRED): the reference to an intent.resourceQuantity (REQUIRED): the amount and unit of the economic resource.The first commitment MUST satisfy the primary intent of the proposal. The second commitment MUST satisfy the reciprocal intent of the proposal (if present).
The units specified in the agreement MUST match the units specified in the proposal. If the value of unitBased property of the proposal is false, the amount of resources specified in commitments MUST be equal to amounts specified in the proposal. If the value is true, amounts MUST be multiples of amounts specified in the proposal. If unitBased property is not present on the proposal, arbitrary amounts can be used.
Example of an Offer(Agreement) activity:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Agreement\": \"vf:Agreement\",\n \"stipulates\": \"vf:stipulates\",\n \"stipulatesReciprocal\": \"vf:stipulatesReciprocal\",\n \"Commitment\": \"vf:Commitment\",\n \"satisfies\": \"vf:satisfies\",\n \"resourceQuantity\": \"vf:resourceQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\"\n }\n ],\n \"type\": \"Offer\",\n \"id\": \"https://social.example/objects/fc4af0d2-c3a1-409b-947c-3c5be29f49b0/offer\",\n \"actor\": \"https://social.example/users/bob\",\n \"object\": {\n \"type\": \"Agreement\",\n \"stipulates\": {\n \"type\": \"Commitment\",\n \"satisfies\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#primary\",\n \"resourceQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"1\"\n }\n },\n \"stipulatesReciprocal\": {\n \"type\": \"Commitment\",\n \"satisfies\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#reciprocal\",\n \"resourceQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"30\"\n }\n }\n },\n \"to\": \"https://market.example/users/alice\"\n}\n"},{"location":"fep/0837/#accepting-an-agreement","title":"Accepting an agreement","text":"The object of Accept activity MUST be the id of the Offer activity previously sent to the actor.
Accept activity MUST have the result property containing the Agreement object. The finalized agreement and corresponding commitments MUST have an id property. If a similar agreement between parties already exists, it MAY be updated and its id re-used. The finalized agreement MUST have an attributedTo property and its value MUST match the actor of the Accept activity.
The quantities specified in the finalized agreement MUST match the quantities specified in Agreement object from the Offer activity.
The finalized agreement MAY have url property containing one or more links to resources associated with the agreement. An example of such resource is a payment page (which can be represented as an FEP-0ea0 link).
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Agreement\": \"vf:Agreement\",\n \"stipulates\": \"vf:stipulates\",\n \"stipulatesReciprocal\": \"vf:stipulatesReciprocal\",\n \"Commitment\": \"vf:Commitment\",\n \"satisfies\": \"vf:satisfies\",\n \"resourceQuantity\": \"vf:resourceQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\"\n }\n ],\n \"type\": \"Accept\",\n \"id\": \"https://market.example/activities/059f08fa-31b1-4136-8d76-5987d705a0ab\",\n \"actor\": \"https://market.example/users/alice\",\n \"object\": \"https://social.example/objects/fc4af0d2-c3a1-409b-947c-3c5be29f49b0/offer\",\n \"result\": {\n \"type\": \"Agreement\",\n \"id\": \"https://market.example/agreements/edc374aa-e580-4a58-9404-f3e8bf8556b2\",\n \"attributedTo\": \"https://market.example/users/alice\",\n \"stipulates\": {\n \"id\": \"https://market.example/agreements/edc374aa-e580-4a58-9404-f3e8bf8556b2#primary\",\n \"type\": \"Commitment\",\n \"satisfies\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#primary\",\n \"resourceQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"1\"\n }\n },\n \"stipulatesReciprocal\": {\n \"id\": \"https://market.example/agreements/edc374aa-e580-4a58-9404-f3e8bf8556b2#reciprocal\",\n \"type\": \"Commitment\",\n \"satisfies\": \"https://market.example/proposals/ddde9d6f-6f3b-4770-a966-3a18ef006930#reciprocal\",\n \"resourceQuantity\": {\n \"hasUnit\": \"one\",\n \"hasNumericalValue\": \"30\"\n }\n },\n \"url\": {\n \"type\": \"Link\",\n \"href\": \"https://pay.example/invoices/7f1f0c81-0108-4c91-9cb1-d38ebccc3aa1\",\n \"rel\": \"payment\"\n }\n },\n \"to\": \"https://social.example/users/bob\"\n}\n"},{"location":"fep/0837/#rejecting-an-agreement","title":"Rejecting an agreement","text":"The object of Reject activity MUST be the id of the Offer activity previously sent to the actor.
Activity MAY contain content property indicating the reason for rejection.
Example:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Reject\",\n \"id\": \"https://market.example/activities/8c05f97f-1531-4b70-9ca8-4ee4a09f36a4\",\n \"actor\": \"https://market.example/users/alice\",\n \"object\": \"https://social.example/objects/fc4af0d2-c3a1-409b-947c-3c5be29f49b0/offer\",\n \"content\": \"Not available\",\n \"to\": \"https://social.example/users/bob\"\n}\n"},{"location":"fep/0837/#confirmations","title":"Confirmations","text":"Economic transaction happens outside the protocol. When both parties complete their parts of the transaction, the proposing party MUST publish a confirmation.
The type and structure of confirmation activity may vary between different marketplaces, but it MUST contain a reference to the Agreement object. The context property is RECOMMENDED for this purpose.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Create\",\n \"id\": \"https://market.example/receipts/ad2f7ee1-6567-413e-a10b-72650cbdc743/create\",\n \"actor\": \"https://market.example/users/alice\",\n \"object\": {\n \"type\": \"Document\",\n \"id\": \"https://market.example/receipts/ad2f7ee1-6567-413e-a10b-72650cbdc743\",\n \"name\": \"Receipt\",\n \"context\": \"https://market.example/agreements/edc374aa-e580-4a58-9404-f3e8bf8556b2\",\n \"published\": \"2023-07-03T14:13:41.843794Z\"\n },\n \"to\": \"https://social.example/users/bob\"\n}\n"},{"location":"fep/0837/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/0ea0/","title":"FEP-0ea0: Payment Links","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2023-04-18 #88 Discussions codeberg"},{"location":"fep/0ea0/#summary","title":"Summary","text":"This FEP describes a way to attach payment information to ActivityPub actors and objects. That information might be a link to donation page, a link for buying an artwork, or anything else that can be represented with a URI.
"},{"location":"fep/0ea0/#history","title":"History","text":"PeerTube videos may have support property, which contains a text explaining how to support the content creator.
FEP-8c3f: Web Monetization was published in 2022. The ensuing discussion on SocialHub forum led to the creation of this proposal.
"},{"location":"fep/0ea0/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/0ea0/#payment-links","title":"Payment links","text":"Payment link is an object with the following properties:
type (REQUIRED): the type MUST be Link.name (RECOMMENDED): the name property SHOULD contain a human-readable description of the payment link.href (REQUIRED): the href property MUST contain a payment URI. This can be a URL of a website, or any other kind of URI, such as 'payto' URI.rel (REQUIRED): the rel property MUST contain the string payment or an array containing that string. The payment relation type is defined in Link Relations Registry.Payment links MUST be added to attachment array of an actor or an object.
Payment link attached to an actor:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Person\",\n \"id\": \"https://social.example/users/alice\",\n \"inbox\": \"https://social.example/users/alice/inbox\",\n \"outbox\": \"https://social.example/users/alice/outbox\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Donate\",\n \"href\": \"payto://iban/DE75512108001245126199\",\n \"rel\": \"payment\"\n }\n ]\n}\n Payment link attached to an object:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Image\",\n \"id\": \"https://gallery.example/photos/123\",\n \"attributedTo\": \"https://gallery.example/users/alice\",\n \"name\": \"Painting of a cat\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Buy\",\n \"href\": \"https://gallery.example/photos/123/order\",\n \"rel\": [\n \"payment\",\n \"https://gallery.example/ns#buy\"\n ]\n }\n ]\n}\n"},{"location":"fep/0ea0/#payment-links-as-actor-metadata","title":"Payment links as actor metadata","text":"(This section is non-normative.)
Implementers may treat payment links attached to actor object in the same way as actor metadata fields. In that case, name translates into field label and href translates into field value.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/1042/","title":"FEP-1042: Peer to Peer Fediverse Identities","text":"Authors Status Date received Tracking issue Discussions Repository Mauve Signweaver contact@mauve.moeDRAFT 2025-04-03 #546 Discussions codeberg"},{"location":"fep/1042/#summary","title":"Summary","text":"ActivityPub's federated model allows for flexibility in referencing data between different instances. However it requires that these instances be always online and do not allow for non-internet or locally published identities outside of the HTTPS/DNS based web.
This document describes how implementors can extend ActivityPub to link to objects hosted on Peer to Peer protocols and how compatible clients should detect this support and load each others' content.
"},{"location":"fep/1042/#rationale","title":"Rationale","text":"ActivityPub's federated model allows for flexibility in referencing data between different instances. However it requires that these instances be always online and do not allow for non-internet or locally published identities outside of the HTTPS/DNS based web. By linking to P2P versions of documents from HTTPS versions, we can enable interoperability with existing AP implementations while opening the way to P2P-only networks.
"},{"location":"fep/1042/#details","title":"Details","text":""},{"location":"fep/1042/#general-approach","title":"General Approach","text":"There are existing specifications in place for linking to \"alternate\" URLs for content. These are already used by implementations to resolve ActivityPub identities from web pages, to link to web pages from ActivityPub objects like an Actor or a Note, or to discover the ActivityPub account of a user referenced with @username@domain syntax with Webfinger.
Our approach builds on these methods by adding extra URLs pointing to P2P versions of resources along side the expected HTTP links.
Compliant clients should attempt to prioritize loading P2P versions of AP data when possible / enabled by users.
"},{"location":"fep/1042/#link-elements-in-html","title":"Link elements in HTML","text":"One of the easiest ways is to sit beside the content negotiation support with links to HTML pages. Statically published AP sites which cannot do content negotiation via Accept headers have the option to point to the ActivityStreams version of a site using a link header pointing to the JSON-LD file for the AP object associated with the page. P2P sites should add extra links after this to the P2P URLs of the site which also use the rel=alternate attribute.
When loading a link to an HTML page, clients should attempt to detect the Link element with the preferred url scheme and resume from there.
Similar to the HTML links, published sites should add the P2P URLs of their AP Actors after the HTTP version within the links section of their JRD document. These links must use \"type\": \"application/activity+json\" and \"rel\": \"self\" along with their P2P URL under href. Make sure to place these P2P links after the HTTP versions to avoid bugs in implementations that check the first Link without looking at the URL scheme.
When attempting to resolve a user from a webfinger style mention like @username@domain.com, clients should first resolve the Webfinger data using standard methods over HTTP. Clients should then loop through the links field and search for \"type\": \"application/activity+json\" and \"rel\": \"self\" objects with an href pointing to a P2P URL.
FEP-fffd set the foundation for linking to alternate versions of content. P2P clients should expand on this by adding links to p2p versions of all AP objecs served via HTTP. Note that the P2P objects should have their id set to their P2P URL and Must link to the HTTP version of the data. Due to how most AP implementations resolve these links, make sure any HTTP links come first in your URL list so that they don't accidentally cause loading errors in implementations lacking support for P2P resolving.
When loading a JSON-LD file associated with an Activitypub object (Actor, Note, Collection, etc), clients should check the url field for objects with type: Link and rel: Alternate with the href property pointing to a URL with the supported P2P protocol.
For accounts that don't want to set up their own HTTP server, an HTTP gateway may be used. In this case, have your P2P site loadable on a gateway on its own subdomain. For example on ipfs.io. From there use this domain as your root for all the URLs for HTTP versions of your data. Have your P2P objects link to the HTTP URLs and vise versa.
Actor HTTP version
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", { \"@language\": \"en- CA\" }],\n \"type\": \"Person\",\n \"id\": \"https://staticpub.mauve.moe/about.html\",\n \"url\": [{\n \"type\": \"Link\",\n \"rel\": \"alternate\",\n \"mediaType\": \"application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"\",\n \"href\": \"ipns://staticpub.mauve.moe/about-ipns.jsonld\"\n }],\n \"outbox\": \"https://staticpub.mauve.moe/outbox.jsonld\",\n \"inbox\": \"https://social.distributed.press/v1/@mauve@staticpub.mauve.moe/inbox\",\n \"following\": \"https://staticpub.mauve.moe/following.jsonld\",\n \"followers\": \"https://staticpub.mauve.moe/followers.jsonld\",\n \"preferredUsername\": \"mauve\",\n \"name\": \"Mauve \ud83d\udc41\ufe0f\",\n \"summary\": \"This is an example static site profile. You can find the source code at https://github.com/RangerMauve/staticpub.mauve.moe/\",\n \"icon\": [{\n \"type\": \"Image\",\n \"mediaType\": \"image/png\",\n \"name\": \"Distributed Press logo\",\n \"url\": \"https://staticpub.mauve.moe/profile.png\"\n }]\n}\n Actor IPNS version:
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", { \"@language\": \"en- CA\" }],\n \"type\": \"Person\",\n \"id\": \"ipns://staticpub.mauve.moe/about-ipns.jsonld\",\n \"url\": [{\n \"type\": \"Link\",\n \"rel\": \"alternate\",\n \"mediaType\": \"application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"\",\n \"href\": \"https://staticpub.mauve.moe/about.jsonld\"\n }],\n \"outbox\": \"ipns://staticpub.mauve.moe/outbox-ipns.jsonld\",\n \"inbox\": \"https://social.distributed.press/v1/@mauve@staticpub.mauve.moe/inbox\",\n \"following\": \"ipns://staticpub.mauve.moe/following-ipns.jsonld\",\n \"followers\": \"ipns://staticpub.mauve.moe/followers-ipns.jsonld\",\n \"preferredUsername\": \"mauve\",\n \"name\": \"Mauve \ud83d\udc41\ufe0f\",\n \"summary\": \"This is an example static site profile. You can find the source code at https://github.com/RangerMauve/staticpub.mauve.moe/\",\n \"icon\": [{\n \"type\": \"Image\",\n \"mediaType\": \"image/png\",\n \"name\": \"Distributed Press logo\",\n \"url\": \"ipns://staticpub.mauve.moe/profile.png\"\n }]\n}\n Webfinger doc
{ \n \"subject\": \"acct:mauve@staticpub.mauve.moe\",\n \"aliases\": [\n ],\n \"links\": [\n {\n \"rel\": \"http://webfinger.net/rel/profile-page\",\n \"type\": \"text/html\",\n \"href\": \"https://staticpub.mauve.moe/about.html\"\n },\n {\n \"rel\": \"self\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://staticpub.mauve.moe/about.jsonld\"\n },\n {\n \"rel\": \"self\",\n \"type\": \"application/activity+json\",\n \"href\": \"ipns://staticpub.mauve.moe/about-ipns.jsonld\"\n }\n ]\n}\n"},{"location":"fep/1042/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/11dd/","title":"FEP-11dd: Context Ownership and Inheritance","text":"Authors Status Date received Tracking issue Discussions Repository Julian Lam julian@nodebb.orgDRAFT 2025-09-11 #675 Discussions codeberg"},{"location":"fep/11dd/#summary","title":"Summary","text":""},{"location":"fep/11dd/#what-is-a-context","title":"What is a context?","text":"FEP 7888 lays out some guidelines for use of the context as a common property between a set of objects when they can be grouped together through reply association (e.g. a reply-tree).
This proposal aims to extend these guidelines further by codifying:
context.attributedTo.The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in [RFC-2119].
"},{"location":"fep/11dd/#context-ownership","title":"Context Ownership","text":"A context MUST have an owner. The following are upgrades to FEP 7888 that pertain to ownership:
context MUST be resolvable.context MUST contain:attributedTo denoting the authority/owner of that context.context.attributedTo MUST resolve to an actor (ActivityPub \u00a74.1 Actor Objects).A resolvable context collection (see FEP f228) declares an owner by setting context.attributedTo to a resolvable URI.
{\n \"id\": \"https://cats.example.org/1/context\",\n \"type\": \"OrderedCollection\",\n \"attributedTo\": \"https://cats.example.org/users/cat\",\n \"items\": [...]\n}\n"},{"location":"fep/11dd/#context-inheritance","title":"Context Inheritance","text":"Inheritance is considered only when a new object being published has a relation (typically via inReplyTo) to another object.
There are situations where a relation exists but context is explicitly not inherited (e.g. forked topic, quote post). These situations are out of scope of this FEP.
"},{"location":"fep/11dd/#as-a-publisher","title":"As a publisher","text":"The object MUST inherit context from the root node, if the property is present and resolvable. Otherwise the object MUST NOT publish a context.
Implementors SHOULD map that inherited context to a local identifier (if applicable) to support future use-cases/activities.
When publishing an object with a context property outside the local domain, the context owner SHOULD be addressed (to, cc, audience).
Note
If an object is at the root/top-level, then there is nothing to inherit and a new context should be generated.
"},{"location":"fep/11dd/#as-a-consumer","title":"As a consumer","text":"When consuming an object declaring a context outside the local domain, the object's membership MAY be verified by resolving the context directly.
Note
The remote context's server may not reflect the object's membership in a timely manner due to delays in processing (i.e. network congestion, approval queues, etc.)
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/1311/","title":"FEP-1311: Media Attachments","text":"Authors Status Date received Tracking issue Discussions Repository Helge @helge@mymath.rocksDRAFT 2024-12-08 #459 Discussions codeberg"},{"location":"fep/1311/#summary","title":"Summary","text":"Media Attachments are ubiquitous in the Fediverse. My quick investigation into the explore tab on mastodon.social yields that about half the posts contain an image attachment. The mechanism for these is poorly documented. For example, it is not mentioned in ActivityPub.
My goal in this FEP is to document current usage, and issue recommendations on how to improve it. These recommendations are based on the support table Recommended Media Attachment Format available at FunFedi.dev.
For developers that enjoy making their keyboards smoke, I believe that the above link combined with the content of Testing should be enough to adapt their Fediverse applications. The other parts are meant for people, who which to improve the situation related to media attachments.
"},{"location":"fep/1311/#basic-format","title":"Basic format","text":"We will discuss our basic suggested format with the following example of an image attachment.
{\n \"type\": \"Image\",\n \"name\": \"A beautiful cow\",\n \"url\": \"http://pasture-one-actor/assets/cow.jpg\",\n \"width\": 100,\n \"height\": 162,\n \"mediaType\": \"image/jpeg\",\n \"digestMultibase\": \"zQmaeDPzhNL32WQZnnzB1H6QJWvvFNEHdViDB71yrxyXU1t\",\n \"size\": 9045\n}\n There is a lot to say here, first how does this relate to communication in ActivityPub which is done by activity. For this consider the activity (taken from data.funfedi.dev) given by
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/credentials/v2\",\n {\n \"size\": \"https://joinpeertube.org/ns#size\"\n }\n ],\n \"type\": \"Create\",\n \"actor\": \"http://pasture-one-actor/actor\",\n \"to\": [\n \"http://akkoma/users/witch\",\n \"https://www.w3.org/ns/activitystreams#Public\"\n ],\n \"id\": \"http://pasture-one-actor/actor/S5Szzuugy50\",\n \"published\": \"2024-12-05T08:18:48Z\",\n \"object\": {\n \"type\": \"Note\",\n \"attributedTo\": \"http://pasture-one-actor/actor\",\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\",\n \"http://akkoma/users/witch\"\n ],\n \"id\": \"http://pasture-one-actor/actor/qDqgbPpNQPw\",\n \"published\": \"2024-12-05T08:18:48Z\",\n \"content\": \"Recommended Image Format\",\n \"attachment\": [\n {\n \"type\": \"Image\",\n \"name\": \"A beautiful cow\",\n \"url\": \"http://pasture-one-actor/assets/cow.jpg\",\n \"width\": 100,\n \"height\": 162,\n \"mediaType\": \"image/jpeg\",\n \"digestMultibase\": \"zQmaeDPzhNL32WQZnnzB1H6QJWvvFNEHdViDB71yrxyXU1t\",\n \"size\": 9045\n }\n ]\n }\n}\n We note that the media attachment is contained in the array of attachment of the Note object. Furthermore, in difference to the activity and the object, there is no id property nor actor or attributedTo property. This is on purpose, as those are inherited from the object the media attachment is attached to. See Content Licensing for discussion about not having an attributedTo property.
In particular, one should emphasize that a media attachment not having an id is useful to signify that it is not useful as an object without the note, it is attached to.
To use media attachments, an object MUST have an attachment property, whose value is an array containing objects. Furthermore, the contained objects MUST have a type property. For it to be a media attachment the type property MUST be Audio, Image, or Video. However, the attachment property MAY contain other form of attachments, e.g. FEP-0ea0: Payment Links.
This can be expressed as the json-schema:
{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"type\": \"object\",\n \"properties\": {\n \"attachment\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"examples\": [\"Audio\", \"Image\", \"Video\"]\n }\n },\n \"required\": [\"type\"]\n }\n }\n },\n \"required\": [\"attachment\"]\n}\n We now discuss the specific form a media attachment. In addition to type, a media attachment MUST also contain an url property providing the link to the media. The url property MAY also be an array, see Multiple Media Versions. However, this just to be future proof.
Furthermore, media attachments SHOULD contain a name property providing an alternative plain text description of the media object.
Again this can be represented as a json-schema.
{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"type\": \"object\",\n \"properties\": {\n \"type\": {\n \"type\": \"string\",\n \"enum\": [\"Audio\", \"Image\", \"Video\"]\n },\n \"name\": {\n \"type\": \"string\",\n \"examples\": [\"A beautiful cow\"]\n },\n \"url\": {\n \"type\": [\"string\", \"array\"],\n \"examples\": [\"http://you.example/image.png\"]\n }\n },\n \"required\": [\"type\", \"url\"]\n}\n"},{"location":"fep/1311/#current-state-of-type","title":"Current state of type","text":"Many current implementations use Document for media attachments. This has the distinct disadvantage to using Audio, Image, or Video that the parser needs to inspect more content than type to discover what type of attachment it is.
Let's write a bit of python to determine if something is a media attachment based on mediaType, discussed later.
def is_media(attachment:dict) -> bool:\n media_type = attachment.get(\"mediaType\")\n if media_type is None:\n ... # handle error case\n\n main_type, _ = media_type.split(\"/\", 1)\n\n return main_type in [\"audio\", \"image\", \"video\"]\n This already is fairly complicated, but there is more. If one allows url to be an array, one needs a different check, so it turns into something like:
def is_media(attachment:dict) -> bool:\n url = attachment.get(\"url\")\n if isinstance(url, list):\n return is_media(url[0])\n\n media_type = attachment.get(\"mediaType\")\n if media_type is None:\n ... # handle error case\n\n main_type, _ = media_type.split(\"/\", 1)\n\n return main_type in [\"audio\", \"image\", \"video\"]\n There are more exceptions and poor configuration to treat, e.g. url could be an empty list, or the implementation could attach mediaType to the full object instead of the Link.
The goal of a specification needs to be to simplify the code that needs to be written, so we insist on people using the types Audio, Image, and Video for media attachments.
In this section, we discuss properties related to the linked file. The linked file is retrieved either by performing a GET request on the value of url or on href of the Link objects if url is an array. Just to mention it, this requirement might change, once Authentication and Authorization is dealt with.
Let's look back at our original example of a media attachment
{\n \"type\": \"Image\",\n \"name\": \"A beautiful cow\",\n \"url\": \"http://pasture-one-actor/assets/cow.jpg\",\n \"width\": 100,\n \"height\": 162,\n \"mediaType\": \"image/jpeg\",\n \"digestMultibase\": \"zQmaeDPzhNL32WQZnnzB1H6QJWvvFNEHdViDB71yrxyXU1t\",\n \"size\": 9045\n}\n Here the linked file is given by a GET on http://pasture-one-actor/assets/cow.jpg and the result would be
The properties mediaType, digestMultibase, and size could be valid for any attached file, even a non media one, e.g. a text document. Let's quickly review them. mediaType is defined in the ActivityStreams Vocabulary. It describes the MIME type and tells us important information on how to render the file.
digestMultibase is defined here as part of Verifiable Credential Data Integrity. The encoding of a digest in multibase with multihash is somewhat different to the rest of multicodec, because one first has a byte to indicate the format, then another one to indicate the length. This means in particular that all digestMultibase using sha-256 will start with zQm, the z indicating base58 encoding. Checking the digest is important to ensure integrity. As media is often hosted off site using S3, this seems important. For another usage see Content Addressed Storage.
Finally size being the file size in bytes is borrowed from PeerTube. The size should tell us if we want to preload the media or not.
There is something missing in the file properties: access control, see the section Authentication and Authorization in the open questions below.
"},{"location":"fep/1311/#specifying-file-properties","title":"Specifying file properties","text":"The creator of a media attachment SHOULD include the values of mediaType, digestMultibase, and size. The consumer of a media attachment SHOULD ensure integrity of the downloaded attachment based on digestMultibase, i.e. check the digest. The consumer of a media attachment SHOuLD decide based on size and mediaType the best way to consume the attachment.
size and mediaType become more relevant when multiple versions of the media attachment are provided. For example, this could mean that in one feeds one only sees the low quality video by default.
We have now discussed all properties of our example document except for width and height. These properties are only relevant for an image and a video, but not for audio. Similarly, audio and video can have a duration, which images don't. Finally, Mastodon has introduced the additional properties
focalPointblurHashwhere at least focalPoint is user defined. There are a lot of other properties one can consider for media, e.g.
locationfpsIn order to standardize these things further work is needed.
"},{"location":"fep/1311/#multiple-media-versions","title":"Multiple Media Versions","text":"As it is currently not supported in the Fediverse, I will just give the basic example how to use multiple attached Links:
{\n \"type\": \"Video\",\n \"name\": \"A beautiful cow eating\",\n \"url\": [\n {\n \"type\": \"Link\",\n \"size\": 54373,\n \"digest\": \"zQmSzK5qEe5tpjwGMhmjx9RvVoPkWhEmCwxP2s7wPMpKMoK\",\n \"width\": 256,\n \"height\": 144,\n \"href\": \"http://pasture-one-actor/assets/cow_eating.mp4\",\n \"mediaType\": \"video/mp4\"\n },\n {\n \"type\": \"Link\",\n \"size\": 2271723,\n \"digest\": \"zQme2X4rgWuRdmAtGGMSEbdoeRQ2NAL2VptcdRGTYDZbSKG\",\n \"width\": 1920,\n \"height\": 1080,\n \"href\": \"http://pasture-one-actor/assets/cow_eating_hd.mp4\",\n \"mediaType\": \"video/mp4\"\n }\n ],\n \"duration\": \"PT3S\"\n}\n As the example shows, this is useful to attach both a low quality version (54kb) and a high quality one (2.2MB) of a video.
We think that supporting this will open the door for richer applications.
"},{"location":"fep/1311/#testing","title":"Testing","text":"By using json-schema, one can validate some level of correctness of generated media attachments. Relevant schemas are available at Fediverse schemas for media attachments. They can be combined into a feature test using Gherkin, see Media Format.
If you wish to validate everything, including digest, you can use the examples provided at FunFedi.dev.
"},{"location":"fep/1311/#open-question","title":"Open Question","text":"This section is essentially a todo list for the community on stuff that should be fixed, but isn't yet.
"},{"location":"fep/1311/#content-licensing","title":"Content Licensing","text":"The picture in the examples was created based on this picture available for free on pixabay by photographer derekmuller. Unfortunately, the current standards to not let me attach this information to my media object.
One could now say that this could be solved with just using the attributedTo property. Unfortunately, this has a lot of drawbacks. For example derekmuller is not an ActivityPub actor. Also attributing my cropped low resolution image to him, might be something he does not appreciate. Finally, just attributing this image is probably not enough, one should also inform people on how it is licensed.
See FEP-c118 and its discussions for more on the topic.
"},{"location":"fep/1311/#authentication-and-authorization","title":"Authentication and Authorization","text":"Currently, image links must be accessible without any form of authorization. This is due while communication between a user and their server requiring authentication and between servers requiring authentication, images are often stored on third party services, e.g. S3, thus adding authentication is hard.
For some approaches to resolve this see this Fediverse discussion.
One approach to achieve authentication and authorization easily with existing technologies would be Bearcaps.
For a different approach see also Binary Fediverse transport.
"},{"location":"fep/1311/#content-addressed-storage","title":"Content addressed storage","text":"Storing media is costly. It is thus important to avoid duplication. By having a digest for all media through the digestMultibase property, we can use this to index our media storage. This means that before downloading a file, we can check if we already have it.
Consider posting a song, e.g. something from the brat summer, then you might want to attach the album cover, e.g. an image featuring the color #8ACE00. Maybe you will also want to attach some lyrics. This means that your media content contains three parts of separate media type.
One might want to extend the schema for media attachments to convey this information.
"},{"location":"fep/1311/#binary-fediverse-transport","title":"Binary Fediverse transport","text":"A failure of ActivityPub is that it restricts transport to be JSON. ActivityPub thus forces people to use external means, e.g. download the file, to convey media content.
One could solve some problems, e.g. Authentication and Authorization, by just allowing transport of binary blobs. This would require an extension of the wire format.
Allowing for messages to contain binary blobs would also for sharing media via thick clients.
"},{"location":"fep/1311/#references","title":"References","text":"Apparently Streams has some mechanism for protecting attachments. Media URLs in non-public posts look like this: https://{domain}/photo/{filename}.jpg?token={token}
IIRR at least in Hubzilla that token is just part of OpenWebAuth's \"magic authentication\". Where I guess the token contains info about which instance(s?) to contact in order to verify your identity. The audience is kept in the media server(s) database and sync'ed between clones.
"},{"location":"fep/1311/#copyright","title":"Copyright","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/1580/","title":"Index","text":"hide: - navigation description: \"(This section is non-normative) Prior FEPs (FEP-7628, FEP-E965) describe an ability for an Actor to move to a new id, often hosted on a different server instance, however they do not describe a mechanism for moving objects that are owned4 by that actor. This FEP describes a mechanism of migrating objects owned by a moved Actor to the target instance using two OrderedCollections created by the target instance a migration collection that contains a mapping from source object URIs to new URIs on the target instance, and a moves collection that contains the actor Move activities that prove a migration has taken place and allows verification of object signatures in the case the source Actor is no longer available. This FEP attempts to balance effectiveness, performance, security, and ease of implementation by allowing 3rd-party instances to gradually update their local copies of the affected Objects. This FEP describes a \"Push\"-style migration6 initiated by a source instance followed by a \"pull\" of objects by a target instance, as well as a \"Pull\"-style migration initiated by a target instance given a prior export of actor data. The migration operation is agnostic to the type of the Objects being migrated, supporting protocol evolution to unanticipated Object types across instances with varying support for them. Collection-based object migration is orthogonal to, and compatible with content-addressed or other portable object schemes (e.g. FEP-ef61). tl;dr to migrate objects, create a mapping from the old to new objects on the target instance, and let 3rd-party instances gradually migrate their local representations using that map.\"
"},{"location":"fep/1580/#fep-1580-move-actor-objects-with-a-migration-collection","title":"FEP-1580: Move Actor Objects with amigration Collection","text":"Authors Status Type Date received Tracking issue Discussions Repository Jonny Saunders j@nny.fyi DRAFT implementation 2025-10-09 #702 Discussions codeberg"},{"location":"fep/1580/#summary","title":"Summary","text":"(This section is non-normative)
Prior FEPs (FEP-7628, FEP-E965) describe an ability for an Actor to move to a new id, often hosted on a different server instance, however they do not describe a mechanism for moving objects that are owned4 by that actor.
This FEP describes a mechanism of migrating objects owned by a moved Actor to the target instance using two OrderedCollections created by the target instance:
migration collection that contains a mapping from source object URIs to new URIs on the target instance, andmoves collection that contains the actor Move activities that prove a migration has taken place and allows verification of object signatures in the case the source Actor is no longer available.This FEP attempts to balance effectiveness, performance, security, and ease of implementation by allowing 3rd-party instances to gradually update their local copies of the affected Objects. This FEP describes a \"Push\"-style migration6 initiated by a source instance followed by a \"pull\" of objects by a target instance, as well as a \"Pull\"-style migration initiated by a target instance given a prior export of actor data. The migration operation is agnostic to the type of the Objects being migrated, supporting protocol evolution to unanticipated Object types across instances with varying support for them. Collection-based object migration is orthogonal to, and compatible with content-addressed or other portable object schemes (e.g. FEP-ef61).
tl;dr: to migrate objects, create a mapping from the old to new objects on the target instance, and let 3rd-party instances gradually migrate their local representations using that map.
"},{"location":"fep/1580/#problem-statement","title":"Problem Statement","text":"(This section is non-normative)
Following an Actor migration, the new, \"target\" Actor is considered equivalent to and the successor of the prior \"source\" Actor. Despite being declared as equivalent2 as:alsoKnownAs, activities and objects on a cooperative source instance continue to exist under the ownership of the now-inactive Actor, effectively becoming \"zombie objects\" that the new primary Actor has no direct control of; on an uncooperative or unavailable instance, these objects are lost even with a valid migration activity. Inability to migrate the objects owned by an Actor means that they are catastrophically lost in the case of instance shutdown, and has far-ranging impacts on the social and political structure of the fediverse by strongly dis-incentivizing movement between instances, and makes new user entry into the fediverse difficult and confusing, where a choice of instance made before having the chance to gain familiarity with the network becomes indefinite destiny.
Moving objects is hard: - The combinatorics of every third-party instance needing to update every object after a move make object migration potentially forbiddingly costly. This is particularly true when considering ActivityPub in \"pub/sub-only\" mode where actions must be processed immediately upon receipt: a target server emitting 100,000 Move activities, one for each post, that all recipients need to keep up with would drown the fediverse. - The mapping from old to new URIs is not knowable in advance, as the local IDs used by one instance software need not map onto the IDs used by another, so a single batch update action is impossible. - There is no guarantee that a given Object on the source instance will be fully supported by the target instance, and some non-uri properties like proof signatures by definition must change after a migration, so 3rd-party instances must query the target instance to retrieve the updated versions of the migrated objects. - Objects are embedded within a larger graph that includes other collections like replies that must also be updated to reflect the migration. - Not all objects should be migrated, e.g. someone may want to make a \"fresh start\" on a new instance, keeping their followers/followed accounts, but only bringing some subset of objects (or none) along with them. - The source instance may not be cooperative or even online, so object migration may require the use of external backups which may be lossy or out of date. - Instance software that does not support object migration may be left in an inconsistent state with the rest of the fediverse that does.
However ActivityPub has a dual push and pull operation, where activities and objects can be stored in Collections for at-will consumption by 3rd-party instances. By storing a mapping between prior object URIs/IDs on the source instance and new URIs/IDs on the target instance, object migration can be done gradually, partially, safely, and selectively. This approach uses standard ActivityPub semantics and common collection enumeration operations, easing implementation.
MUST, MAY, and SHOULD used in the RFC-2119 sense where they appear in CAPITAL LETTERS.
Supporting instances MUST indicate their support of this FEP by including its namespace in the @context of affected Actor objects.
As used in this document:
Move specifications in FEP-7628 and FEP-E965 where the primary actor of a set of linked identities is changed. Specifically, a \"irreversible\" migration of the movedTo variety, rather than the reversible, partial copiedTo. The \"Actor move\" activity is addressed to as:Public.Objects that are affected by the object migration.Offer[Move] activity emitted by the source instance addressed to the target instance's shared inbox (and not as:Public) after the source instance has validated the source and target actors are FEP-7628 linked actors in order to solicit an Accept[Move] from the target instance confirming the move. It is distinct from the \"actor move\" Move activity that is publicly emitted following acceptance of the move proposal.CURIE prefixes used in this document: - migration: https://w3id.org/fep/1580/ - the namespace created by this FEP (FEP-888d) - as: https://www.w3.org/ns/activitystreams - the ActivityStreams vocabulary - sec: https://w3id.org/security# - The Data Integrity vocabulary used by FEP-8b32 proofs.
The prefixes are assumed to be declared in the @context of any JSON-LD examples. Prefixes are included on first mention of a non-activitystreams term, but are otherwise omitted for concision.
Supporting software MUST support the following behavior specified in other FEPs - FEP-7628 - FEP-8b32 - specifically the software must be capable of managing keypairs per actor rather than use one private key per instance.
All behavior described in this FEP MUST follow receipt of a valid Move activity or its side effects as defined by FEP-7628, FEP-E965 and this FEP that inactivates the source account: i.e. the source account is either no longer available or has a movedTo property declared.
After an Actor Move activity is completed and verified, the target instance MUST perform an \"ingest routine\" that - enumerates all supported actor collections present in the actor object OR the set of objects approved during the moderation handshake, as described in Pre-Move Migration - ingests their contents, and - creates new Activities and Objects that preserve as much of their content as is supported.
\"Supported actor collections\" is left purposefully undefined and is left as an implementation detail to supporting software, however supporting software SHOULD attempt to support all collections and objects that it has a representation for. If unknown object types are encountered, the target instance MAY store a full copy of those objects in case a future software update supports them.
If applicable, objects that control the visibility of other objects (in particular: Block activities) MUST be ingested first so they may be used to control the visibility of objects within the migration collection. Migrated items MUST be subject to the same visibility constraints as on the source instance, including blocks, whenever possible.
During collection enumeration, requests MUST be signed as being on behalf of the target Actor if signing a request as being on behalf of an actor is supported by the source and target instances.
Created/updated timestamps MUST be preserved from the original object, but supporting instances MAY add and MAY display additional properties to indicate an object has been migrated: - a migration:migratedAt timestamp to display the time an object was migrated, and - a migration:migratedFrom URI to link to the prior object.
The target instance MUST insert the newly migrated objects in relevant collections and perform any side effects that normally accompany object creation, however it SHOULD NOT emit Activities indicating the creation of these migrated objects.
Instead, the target instance MUST create two collections on the Actor object to support gradual migration:
"},{"location":"fep/1580/#migration-collection","title":"migration Collection","text":"The target instance MUST create a migration5 OrderedCollection that maps the object URIs from the source instance to their counterparts on the target instance. The migration collection SHOULD remain present on the actor object for the lifetime of the actor.
The migration collection MUST be ordered in reverse chronological order by the timestamp that the migrated counterpart of the original object is created on the target instance (NOT according to the date the original object was created or updated on the source instance).
The migration collection MUST contain a migration:moves URI reference to the accompanying moves collection (below).
The items in the migration collection SHOULD use URI references in their object and target fields rather than inlining the materialized objects, and SHOULD paginate the collection as OrderedCollectionPages to reduce resources spent serializing and constructing the collection.
Items in the migration collection MUST match the item visibility of the source objects, when possible. In particular: - Move activities for objects that are not addressed to as:Public MUST not be publicly displayed. - Move activities for non-public objects MAY be displayed when the request is authenticated as having been made by an Actor in the object's audience3. - Move activities MUST respect actor-level visibility controls like blocks.
The migration collection MAY be created incrementally, inserting migrated objects as they are ingested, or it may only populate the collection upon completion of the ingest routine.
Implementing software MAY provide appropriate options and interfaces to allow the migrating actor to select which, if any objects they want to migrate (see Pre-Move Moderation). If the actor elects to not migrate any objects, the migration collection MUST still be published and its items field MUST be empty9.
Upon completion of the ingest routine, the migration collection's migration:migrationComplete property must be set to true, indicating for consuming instances that for the given state of the moves collection (below) there will be no changes to the migration collection. To \"reopen\" the migration collection for consumption by third party instances, signaling new changes from e.g. an upload of an exported collection (below), the target instance MUST emit another (potentially duplicate) Move activity and set the migrationComplete property to false until the changes are completed. The target instance MAY delay setting migrationComplete to true in order to provide an interface to prompt the migrating actor to upload a previously exported collection of objects.
A target instance MUST NOT begin another actor move to another instance until the migration is complete in order to prevent loss of objects still in the process of being migrated from the source instance.
"},{"location":"fep/1580/#examples","title":"Examples","text":"(This section is non-normative)
(\"...\": \"...\" is used to indicate \"the rest of the object\" in the absence of JSON comments)
Actor on target instance
{\n \"id\": \"https://example.com/chuckTargetson\",\n \"type\": \"Person\",\n \"...\": \"...\",\n \"migration\": \"https://example.com/chuckTargetson/migration\",\n \"moves\": \"https://example.com/chuckTargetson/moves\"\n}\n migration OrderedCollection
{\n \"id\": \"https://example.com/chuckTargetson/migration\",\n \"type\": \"OrderedCollection\",\n \"attributedTo\": \"https://example.com/chuckTargetson\",\n \"totalItems\": 100,\n \"first\": \"https://example.com/chuckTargetson/migration/page/0\",\n \"migrationComplete\": false,\n \"moves\": \"https://example.com/chuckTargetson/moves\"\n}\n migration OrderedCollectionPage
{\n \"id\": \"https://example.com/chuckTargetson/migration/page/0\",\n \"type\": \"OrderedCollectionPage\",\n \"partOf\": \"https://example.com/chuckTargetson/migration\",\n \"next\": \"https://example.com/chuckTargetson/migration/page/1\",\n \"items\": [\n {\n \"type\": \"Move\",\n \"actor\": \"https://source.example.com/barbaraSourceworth\",\n \"origin\": \"https://source.example.com/barbaraSourceworth/posts/12345\",\n \"target\": \"https://example.com/chuckTargetson/items/98765\"\n },\n {\n \"type\": \"Move\",\n \"actor\": \"https://source.example.com/barbaraSourceworth\",\n \"origin\": \"https://source.example.com/barbaraSourceworth/posts/abcde\",\n \"target\": \"https://example.com/chuckTargetson/items/zyxwp\"\n }\n {\"...\": \"...\"}\n ]\n}\n"},{"location":"fep/1580/#moves-collection","title":"moves Collection","text":"The moves collection serves two purposes: - As a content-hashable indicator of the state of an account's migrations - As a durable source of authentication for confirming the validity of the items in the migration collection.
The moves collection contains the deduplicated Move activities emitted when an Actor is migrated. Move activities MUST be signed by the source actor using an FEP-8b32 proof. The move collection MUST make a copy of the source Actor object that contains the means of validating object integrity proofs available either inlined in the items of the Move collection or in the actors collection (described below). Source instances SHOULD inline the source Actor object in actor Move activities.
If the actor is not inlined in a signed Move activity, the target instance MUST create an inlined copy of the source Actor object in its actors collection. The actors collection is an OrderedCollection of inlined Actor objects sorted in lexicographic order according to their id (example below).
The moves collection MUST itself be signed with an FEP-8b32 proof. By including the inlined Actor objects and Move activities signed by the source Actor(s), and having the collection be signed by the target Actor, the moves collection serves as a self-contained, verifiable, bidirectional proof of the validity of the items in the migration collection.
Consuming instances MAY consider a hash of the canonicalized form of the collection (e.g. RFC 8785, RDF canonicalization) as a content hash for the purposes of change detection, but the details of content addressing activitypub objects are outside the scope of this FEP.
The moves collection MAY include a full chain of account movement, including moves from prior instances to the source account where the target is not the current target account. Inclusion of account move activities MAY be used by consuming instances to migrate objects across several moves, but specifying chains of migration is out of scope for this FEP.
(This section is non-normative)
moves collection with Actors inlined in Move activities
{\n \"id\": \"https://example.com/chuckTargetson/moves\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n {\n \"id\": \"https://source.example.com/activities/12345\",\n \"type\": \"Move\",\n \"origin\": \"https://source.example.com/barbaraSourceworth\",\n \"target\": \"https://example.com/chuckTargetson\",\n \"actor\": {\n \"id\": \"https://source.example.com/barbaraSourceworth\",\n \"type\": \"Person\",\n \"...\": \"...\",\n \"publicKey\": {\n \"id\": \"https://source.example.com/barbaraSourceworth#main-key\",\n \"owner\": \"https://source.example.com/barbaraSourceworth\",\n \"publicKeyPem\": \"-----BEGIN PUBLIC KEY-----\\n...\"\n }\n },\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://source.example.com/barbaraSourceworth#main-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n }\n ],\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://example.com/chuckTargetson#main-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n}\n moves collection with Actors inlined in the actors collection
{\n \"id\": \"https://example.com/chuckTargetson/moves\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n {\n \"id\": \"https://source.example.com/activities/12345\",\n \"type\": \"Move\",\n \"origin\": \"https://source.example.com/barbaraSourceworth\",\n \"target\": \"https://example.com/chuckTargetson\",\n \"actor\": \"https://source.example.com/barbaraSourceworth\",\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://source.example.com/barbaraSourceworth#main-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n }\n ],\n \"actors\": {\n \"id\": \"https://example.com/chuckTargetson/moves#actors\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n {\n \"id\": \"https://source.example.com/barbaraSourceworth\",\n \"type\": \"Person\",\n \"...\": \"...\",\n \"publicKey\": {\n \"id\": \"https://source.example.com/barbaraSourceworth#main-key\",\n \"owner\": \"https://source.example.com/barbaraSourceworth\",\n \"publicKeyPem\": \"-----BEGIN PUBLIC KEY-----\\n...\"\n }\n }\n ]\n },\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://example.com/chuckTargetson#main-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n}\n"},{"location":"fep/1580/#migrations-from-exported-collections","title":"Migrations from Exported Collections","text":"In circumstances where the original objects are not available at the source instance, importing objects from some external backup of the relevant collections is no different than enumerating them from the source instance. If a valid Move activity was emitted, supporting instances SHOULD provide a means of uploading and ingesting exported collections, and if they do, they SHOULD ingest and otherwise handle objects from an export format of a collection identically as those that were retrieved from the source instance, ignoring any duplicates.
Recall this FEP assumes that a valid, signed Move activity was emitted by the source instance - the case where an account Move activity was not emitted and the source instance is unavailable or hostile is outside the scope of this FEP. However, if the actor export contains the private key that accompanies the public key used for object proofs, and a Move activity can be created and validated independently of the source instance, the actor Move activity SHOULD be considered equivalent to one emitted by the source instance (FEP-7628 \"Pull\" mode migrations). Supporting software MAY provide interfaces and mechanisms for import and export of private actor keys and creation of Move activities from those exports.
Target instances MAY treat imports from exported collections as subject to the same moderation flow they would apply to objects imported from a source instance (see Pre-Move Moderation) and choose objects to remove or modify from an import. If moderation is performed, the target instance MUST notify the target actor of removed or modified objects.
See notes on security below re: source object forgery.
"},{"location":"fep/1580/#rate-limiting","title":"Rate Limiting","text":"The target instance is the only instance that can know the rate at which it can service requests. Accordingly, the target instance SHOULD use standard HTTP rate limit headers and response codes to coordinate gradual migration requests by 3rd-party instances.
The target instance SHOULD service migration-related requests as quickly as possible without otherwise degrading normal service so that time spent with third-party instances in an inconsistent state is minimized.
"},{"location":"fep/1580/#source-instance","title":"Source Instance","text":"Comparatively little needs to be implemented on the source server.
If authentication and signing requests as being made on behalf of an actor is supported by the source and target instances, the source server MUST consider requests made on behalf of the of the target Actor as if they were made by the source Actor for purposes of visibility and access.
Source instances MUST make objects that control object visibility (e.g., blocks) available to target instances during migration in order to preserve Actor visibility preferences. If any such visibility control objects are not typically present in an Actor object's linked Collections, the source instance MUST include them in Collections linked from the Actor object, and they SHOULD use keys that can be predicted from the object type, e.g. blocks for Block activities, though the specific terms and Collection structure are left unspecified here7.
Source instances SHOULD: - continue to host original copies of the migrated objects until the target instance completes its ingestion. - prompt source actors to export any objects that were rejected or changed during moderation (see Pre-Move Moderation), and attempt to store them for some reasonable period to allow an export to be made. - consume the migration collection from the target instance and store a mapping from the source to target objects. - present the migrated objects with a FEP-7628 movedTo property containing the new target object URI while the object remains present - if the source object is removed, return a 301 redirect to the target object URI.
However, source instances MAY elect not to preserve the original objects for one of several reasons e.g. in FEP-73cd re: moderation, legal, or other concerns that make continued hosting of the source objects intolerable to the source instance. This FEP is written to minimize the responsibility of the source instance, purposefully avoiding binding it into longterm service requirements by making a self-contained record of moves and migration on the target instance.
After observing and validating an account Move activity, 3rd-party instances MUST initiate a \"migration routine\" (below) that consumes the migration collection of the target Actor.
If the Move activity was not observed by a 3rd-party instance (e.g. because the instance was offline or unreachable), a 3rd-party instance MUST initiate the \"migration routine\" upon observing either: - a FEP-7628 movedTo property in the source Actor object AND reciprocal alsoKnownAs property in the target Actor, or - the presence of an unprocessed Move action in the target Actor's moves collection.
If the migration collection is not marked as complete (via migrationComplete == true), 3rd-party instances MUST periodically check for new items, with some long timeout in case the target instance is malfunctioning or incorrectly implements migrationComplete.
3rd-Party instances MUST validate the object integrity proof for actor Move activities, but are NOT REQUIRED to re-validate each of the migrated items.
If after observing a valid Move activity, 3rd-party instance observes a mismatch in migration data (e.g. mapping to different URIs, differences in content) between the source and target instances, it MUST prefer the target instance's data, as the Move activity has designated the target Actor and target instance as the primary Actor in custody of the objects.
For an actor Move activity, 3rd-party instances MUST update all references to the source Actor in its internal representations of objects to refer to the target Actor - e.g. in \"mentions\" or as:Links. They MAY preserve representations of both the source and target actor for the purposes of representing and displaying the Move.
For each entry in the migration collection that the 3rd-party instance has a local representation of, 3rd-party instances MUST - Update the item's ID/URI from the origin to the target. - Request any updated fields in the target object from the target instance. - Update its internal object representations so it is indistinguishable from the case where source object was created by the target actor and instance (except for migration-specific fields like migratedFrom).
3rd-party instances MUST NOT emit additional Update or other activities while updating migrated objects.
3rd-party instances SHOULD NOT fetch items referred to in the migration mapping that they do not already have a local representation of: object migration is not backfilling.
3rd-party instances SHOULD implement migration routine operations as optimistic: they should perform predictable operations using their local representations of objects without waiting for full data from the target instance when possible. For example, after consuming the migration collection, which is effectively a mapping from source to target URIs, the instance should update the object's URI without waiting for a full refresh of the object. The target instance MAY update the content of the objects during migration, but 3rd-party instances SHOULD continue to treat them as unchanged until told otherwise by the target instance.
3rd-party instances SHOULD treat interactions (replies, likes, etc.) from other 3rd-party instances as if they have been, or will be migrated as well. 3rd-party instances SHOULD store the relevant migratedFrom and other information needed to reconcile activities and objects from other 3rd-party instances that do not implement object migration: e.g. if actor xena migrated from instance a to instance b, and instance c has a receives an activity from instance d that is inReplyTo object zzz from @xena@a, but instance c knows @xena@a is now @xerxes@b and zzz is now yyy, it should transform the inReplyTo field while processing that activity.
Migration routine operations SHOULD also be patient: requests for migration pages and refreshes of migrated objects should be given ample inter-request delays. The target instance is responsible for communicating rate limits, but 3rd-party instances should also be polite by default and aim to complete, for example, a migration of an Actor with 10,000 objects on the order of ~days to a week.
Migration of extended objects that are referred to by a migrated object SHOULD be performed on a \"best effort\" basis - when there is an unambiguous change to be made (e.g. updating the target for activities in the object's Like collection to refer to the new URI), it should be made, but it is the responsibility of the target instance to include any objects that should be migrated in the migration collection.
The target instance MAY provide some batched or condensed collection of Updates that contain only the changed fields for migrated objects, but specification of such a change collection is outside the scope of this FEP.
If 3rd-party instances passively or periodically refreshes objects from the source or target instance and observe that they have been migrated (e.g. the source instance responds with a 301 redirect to the target object, a movedTo property is present on an object), the 3rd-party instance must verify the existence of a relevant actor Move activity, and if found, perform the migration routine on that object.
As this FEP is implemented and deployed by various fedi server apps, different instances will \"become aware of\" migrations at different times. This FEP supports incremental migration as each different role becomes object migration aware:
migration collections, and when present they should create redirects and populated movedTo fields accordingly.migration and moves collections accordingly. In the initial window of implementation, there will be many previously-migrated accounts that no longer have access to a signed account Move activity to prove a given set of entries in the migration collection. However they should still publish those collections however possible, and allow 3rd-party instances to decide how to handle previously-accepted account Moves. This transition path will be the subject of further expansion and revision as lessons are learned during implementation. In cases where the source instance is no longer available and the moved Actor has an export copy of their prior posts, this FEP should support recovering those posts to the target instance.migration collections encountered while refreshing Actors is a formally spec'd initiation route for migration, so no special consideration should be needed. The gradual deployment of object migration across instances should soften some of the resource burden of needing to migrate a decade of objects.See discussion below (Discussion: Moderation) motivating this section and its design.
Recall that a valid actor Move having already been emitted and accepted is a precondition of this spec. This section8 specifies behaviors before the actor Move activity has been emitted and accepted.
Prior to emitting the publicly-addressed actor Move activity, source and target instances MUST complete the following Proposed Move handshake:
verification: The source instance verifies the source and target actors are Linked Actors (as per FEP-7628)subsetting: OPTIONAL - The source instance allows the source actor to subset the objects they wish to migratesyn: The source instance sends a Offer[Move] \"Proposed Move\" activity addressed to the target instance's shared inbox.moderation: OPTIONAL - The target instance may perform a \"moderation routine\" to remove or update objects within the set of objects to be migratedsyn-ack:Accept[Move] activity addressed to the source actor indicating it accepts the Proposed Move with no changes.TentativeAccept[Move] activity addressed to the source actor with an attached collection of Remove and Update activities.Reject[Move] activity addressed to the source actor, and the source actor is notified that their request to move was rejected.review: If a TentativeAccept[Move] response was received, the source instance provides a means of the source actor reviewing the changes to the migrated items required by moderation and indicating whether they accept them or wish to cancel the actor move operation.ack:Accept[Move] response was received, the public Actor Move activity is emitted.TentativeAccept[Move] was received and the source Actor indicated that they accept the changes required by moderation in the review phase, the public Actor Move activity is emittedTentativeAccept[Move] was received and the source actor indicated that did not accept the changes from moderation and wish to cancel the actor move, a Reject[TentativeAccept[Move]] activity is emitted addressed to the target instance's shared inbox.subsetting: The source instance MAY provide the source actor some means of selecting which objects they choose to migrate. If such a subsetting feature is provided, the resulting subset of objects to migrate MUST be included as an attachment to the Proposed Move. If a collection of objects to migrate is attached to a proposed move, the target instance MUST consider only these objects in the subsequent moderation stage and replace the set of objects that would otherwise be discovered by enumerating the source actor's collections in the ingest routine. An empty attachment collection MUST be interpreted as the intent to migrate only the actor with none of its objects. If an attachment of objects to migrate is absent, the target instance MUST consider the set of objects to migrate as the set of importable objects in the source actor's collections.
moderation: The target instance MAY provide some means of moderating the collection of objects to be migrated. The target instance MUST return a TentativeAccept[Move] activity with an attached collection of Update[Object] and Reject[Object] activities that represents the objects that would be changed or removed in a migration, relative to the collection of proposed objects to migrate. Following the ActivityPub specification of server-to-server Update actions, the complete object with modifications made must be included in Update[Object] responses, rather than a set of partial updates (only those fields that have changed). To avoid specious updates that are trivially implied by an Actor Move (e.g. the proof field changing) or would require the target instance to have already imported the object (e.g. the id field changing), the updated object MUST NOT contain changes to fields that can only be determined after an object is migrated. The source instance MUST NOT validate the updated objects against the proof field - as they will by definition fail the integrity proof.
Collections or OrderedCollections referenced within the attachments of the Proposed Move and TentativeAccept[Move] activities MUST be treated as if the contained objects were included in the attachments directly - proposing to migrate or moderate a Collection means that one is proposing to migrate or moderating all the items in that Collection.
If a valid Actor Move activity is received without having previously accepted a Proposed Move, e.g. from a source instance that does not support this FEP: - If a target instance chooses not to moderate migrated objects, it MUST accept the Actor move activity as otherwise described in this FEP, migrating all objects it is capable of migrating. - If a target instance chooses to moderate migrated objects, it MUST consider all objects it is capable of migrating as being subject to moderation, but MAY choose not to immediately ingest all objects, instead gradually ingest and add them to the migration collection as they are approved. The target instance MUST immediately notify the target actor of the pending moderation and advise them to make a backup of the objects from the source instance to preserve any objects that may be removed or modified by moderation.
If the target instance refuses the initial Proposed Move request, e.g. if it does not support this FEP, the source instance MUST notify the source actor that their objects may not be migrated and allow them to approve or cancel emission of the public actor Move activity.
(This section is non-normative)
Proposed Move with a set of proposed object to migrate
{\n \"id\": \"https://source.example.com/activities/23456\",\n \"type\": \"Move\",\n \"to\": \"https://example.com/sharedInbox\",\n \"origin\": \"https://source.example.com/barbaraSourceworth\",\n \"target\": \"https://example.com/chuckTargetson\",\n \"attachment\": {\n \"type\": \"Collection\",\n \"items\": [\n \"https://source.example.com/barbaraSourceworth/posts/111\",\n \"https://source.example.com/barbaraSourceworth/posts/222\",\n \"https://source.example.com/barbaraSourceworth/followers\"\n \"...\"\n ]\n } \n} \n Proposed Move specifying that no objects should be migrated with an empty collection
{\n \"id\": \"https://source.example.com/activities/34567\",\n \"type\": \"Move\",\n \"to\": \"https://example.com/sharedInbox\",\n \"origin\": \"https://source.example.com/barbaraSourceworth\",\n \"target\": \"https://example.com/chuckTargetson\",\n \"attachment\": {\n \"type\": \"Collection\",\n \"items\": []\n } \n} \n TentativeAccept[Move] specifying a set of removals and updates from moderation
The original proposed collection of objects is modified such that - post 111 would not be migrated - post 222 would have its attachment removed (but otherwise remain unchanged, assuming the \"...\" expands to the original object's remaining fields)
{\n \"id\": \"https://example.com/activities/45678\",\n \"to\": \"https://source.example.com/barbaraSourceworth/inbox\",\n \"type\": \"TentativeAccept\",\n \"object\": \"https://source.example.com/activities/23456\",\n \"attachment\": {\n \"type\": \"Collection\",\n \"items\": [\n {\n \"type\": \"Reject\",\n \"object\": \"https://source.example.com/barbaraSourceworth/posts/111\"\n },\n {\n \"type\": \"Update\",\n \"object\": {\n \"id\": \"https://source.example.com/barbaraSourceworth/posts/222\",\n \"attachment\": null,\n \"content\": \"The thing I originally posted, since server-to-server Updates aren't partial updates\",\n \"...\": \"...\"\n }\n }\n ]\n } \n} \n"},{"location":"fep/1580/#discussion","title":"Discussion","text":"(This section is non-normative)
Feedback and edits are welcome for each of these sections to include the inevitable caveats and consequences unforeseen by the author.
"},{"location":"fep/1580/#out-of-scope","title":"Out of Scope","text":"This FEP is focused exclusively on the circumstance where a valid Move activity exists or can be created, and thus does not consider
Move activity or export a private key to sign one created by the target instance.id schemes typically don't require object migration (indeed that is their primary purpose), and thus this FEP does not apply to them. Implementing object migration for HTTP/URL objects should not be construed as competing with or trading off with instance-independent objects or alternative id or resolution schemes, and is intended to resolve a need in existing systems rather than preventing new systems from being created.copiedTo-style, non-final redirects) are not considered in this FEP, and it assumes that there is a single target instance and actor which becomes the primary Actor in custody of the source actor's objects. It is not possible in this formulation for the source Actor to indicate which objects might be owned by different targets or to resolve conflicts over object ownership between multiple target actors, though future amendments could allow the actor Move activity to contain scoping information that could accomplish that. Merging actors (multiple source actors moving to a single target actor) is explicitly supported by the moves collection, however.Migrating objects from one instance to another involves changing their moderation context: the source and target instances are likely to have differences in rules governing content, behavior, and connections between actors. If migrating objects is subject to different rate limits and moderation control than normal object creation, it may serve as an abuse vector, where content that is not allowed on the target instance can be created at faster rates or out of the eye of moderators1. As a result, target instances may want some means of moderating incoming content.
The potential implementations of moderation for migrated content might vary considerably, and this FEP is not intended to overly constrain them in advance. However since the needs of the migrating actor and the needs of the instance may conflict during moderation, some guidance is provided above (Pre-Move Moderation) to balance them.
Some examples of conflicting needs between involved parties: - The migrating actor has an interest in maintaining whatever subset of their previously-created objects they elect to migrate, but the target instance has an interest in maintaining the norms and rules that govern instance content. There is a risk of permanent loss if the target instance chooses to not ingest some objects during a migration and the source instance chooses to remove them following a migration. - Reviewing a large number of objects might create a heavy moderation burden on target instances, but a migrating actor should not be left in a prolonged state of uncertainty after their account has already been moved regarding whether their content will be allowed to migrate with them. - The migrating actor may wish to revoke their move to a target instance after seeing the results of moderation, but repeated moves create a large amount of traffic on the network, and the target instance should not have to serve as an intermediate host to content it views as against its rules in order to facilitate a second-order move. - Current implementations of FEP-7628 account movements may not be subject to moderation, as they only take into account the actor, not any of its objects. An overly-complex moderation spec in this FEP may prolong its implementation, prolonging the broader harms from the absence of object migration.
The above spec language reflects a strategy for balancing these conflicting needs: - Moderation should take place between the proposal of the Move and its acceptance - Target instances should make modifications from moderation to the collection of migrated objects transparent to the migrating actor. - Actors must have some means of changing their mind about a move if the terms of moderation are unacceptable to them after the target instance reviews any imported content. - If subsetting and moderation are both waived, or the source or target instances do not implement this FEP, the handshake can fallback to behavior equivalent to emitting a public actor move activity without the handshake. This can allow for staged implementation, handling the core migration routines first, and the frontend work needed for moderation afterwards.
The pre-move moderation spec is intended to be very general to accommodate different strategies for managing bulk moderation. For (non-exhaustive) example:
Update for all objects with attached media with that attachment removed, e.g. if storage costs would be prohibitive or an instance doesn't support the content type.Remove for all objects not addressed to as:Public or the \"followers\" collection.The above specification regarding pre-move moderation is compatible with FEP-e965, as it only requires that actors be publicly declared as linked in order for a Move to be considered valid - this spec adds additional optional behavior prior to the Move activity being broadcast to 3rd-party instances without modifying that requirement.
Migrating tens of thousands of objects across hundreds or thousands of instances is an intrinsically costly operation. The strategy here attempts to mitigate extreme resource requirements by allowing migrations to happen gradually and partially. Rather than emitting a flood of thousands of activities, 3rd-party instances can accomplish a majority of the important changes needed by consuming a collection of small Move activities that contain pairs of URIs, and then slowly backfill remaining migration changes over days or weeks. The need for object migration is so high that the resource cost is well worth it, and after the initial period after deployment where all the migrations until the present need to be processed, account migrations are relatively rare (i.e. Mastodon has a 30-day cooldown).
Instances that support object migration should remain interoperable with instances that do not, and that should be achievable by keeping a map from prior to current object IDs and transforming references to migrated objects when processing activities from non-migratory instances. Some inconsistency in network state is inevitable when implementing new behaviors, but this FEP shouldn't introduce new baffling complexities to network state: the current behavior of objects on accounts that have migrated and are no longer active is that they are inert - so additional replies can be created but they won't be received, and so on. Any change that introduces even partial object migration is a net-positive for network coherence.
"},{"location":"fep/1580/#security","title":"Security","text":"The heavy-lifting of secure object migrations is already handled by the existing account Move specs, which establish a trusted means of declaring that one actor is now another actor. This FEP should not introduce new risks for account takeover that are not already inherent in the existing account Move specs. Account Move activities must always be validated by an integrity proof by the source actor, accordingly attempts at account hijacking or spurious attribution must be rejected. However, since there is no mechanism for forcing key expiration or rotation, and thus a compromised secret key may be used to initiate a hostile account migration using an actor export, the Migrations from Exported Collections section is left as an optional recommendation subject to more formal specification in the future.
Importing an external backup introduces the possibility of object forgery. Assuming a valid account Move and proper signing and validation, this is actually not a problem: uploading an export where the author edits all their posts or adds new ones is effectively the same as them batch-posting a bunch of back-dated posts. This FEP does not provide for modifying objects which the target actor does not own, and the target actor may do whatever they please with their objects. 3rd-party instances should be responsible for notifying 3rd-party accounts of changes to objects that they have interacted with, as is done with updates.
Ingesting objects from collections hosted by a hostile source instance poses a larger problem: implementations may want to consider some interface that displays changes made to objects during collection ingestion and allows the target actor to reject them. Uploading some prior export of objects should override the versions of objects served by the source instance, as those should reflect the will of the actor more directly. This is another implementation detail that will require expansion and revision from experiences during implementation.
The author notes they are not a security expert and this FEP and all implementations should be independently audited.
"},{"location":"fep/1580/#privacy","title":"Privacy","text":"No new access routes should need to be established, as all operations use existing Collection mechanisms which must support filtering for object access already. the migration collection should not be an information leak (e.g. by indicating that some private objects exist by referring to their id, which may have more information beyond mere existence, as is the case with the snowflake id scheme used by Mastodon) and only contain information that is already publicly enumerable via public collections on the source actor.
fep-1580.jsonld - The JSON-LD context document to be included in the @context statement of actors on implementing instances.migration.yaml - A linkml schema that provides a concrete form of the ActivityPub/ActivityStreams extensions described in the text. migration.owl.ttl - An OWL schema generated from the LinkML schemaThank you to those that helped provide feedback and recommendations for this FEP:
(in alphabetical order by handle, please feel free to make a PR to add yourself if you helped!) - ansuz - Advice from cryptography POV - apropos - Clarifying questions - silverpill - Suggestions re: clarity and use of ActivityStreams - trwnh - Help with JSON-LD, critical feedback.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/1580/#footnotes","title":"Footnotes","text":"The problem of abusive content being created on an instance is not unique to migrations, and most software implementing ActivityPub has means of creating abusive content rapidly (through API requests and automation) and outside the normal visibility of moderation (with direct messages or other scoped visibility settings). While the problem is not unique, this section attempts to address some new potentials for abuse vectors that may exist given prior implementations of actor moves that did not consider migrating objects along with the actor.\u00a0\u21a9
The description of alsoKnownAs cautions about treatment as an equivalence relationship, however the combination of a movedTo from the source Actor and an inverse alsoKnownAs property from the target Actor does indicate an equivalence relationship where the source Actor is inactive and has become the target Actor. copiedTo does not necessarily indicate an equivalence, as according to the DID spec, linked Actors that are still in use can be used for different purposes. Accordingly we do not consider copiedTo Actor redirects in this FEP.\u00a0\u21a9
Members of as:audience, as:to, as:bto, as:cc, as:bcc, and any other properties used for scoping visibility or delivery.\u00a0\u21a9
\"Ownership\" in this document is defined according to FEP-fe34: Ownership \u21a9\u21a9
the CURIE for the collection is, maybe unhelpfully, migration:migration to refer to https://w3id.org/fep/1580/migration \u21a9
\"Push\" mode migration, as defined in FEP-7628 \u21a9\u21a9
This is possible since the target instance must enumerate all collections linked from an actor document if a specific collection of objects to migrate is not provided by the Offer[Move] handshake.\u00a0\u21a9
Until the next heading.\u00a0\u21a9
The inclusion of an empty migration collection is to differentiate \"a migration that migrated no objects\" from \"an instance that doesn't implement this FEP,\" which would be ambiguous if migration was omitted.\u00a0\u21a9
DRAFT implementation 2024-11-23 #449 Discussions codeberg"},{"location":"fep/171b/#summary","title":"Summary","text":"This document specifies a model for managing conversations in ActivityPub network. It is based on the implementation of Conversation Containers in Streams.
In this model conversations are represented as collections controlled by a single actor. Such conversations take place within a specific audience and may be moderated.
"},{"location":"fep/171b/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/171b/#containers","title":"Containers","text":"A conversation container is implemented as a collection. Every item in that collection is an Add activity where object is another activity (such as Create, Update, Delete, Like, Dislike, EmojiReact or Announce). The conversation owner distributes Add activities to other participants, thus keeping their views of the conversation synchronized.
sequenceDiagram\n actor Alice\n actor Bob\n actor Charlie\n Note right of Alice: Alice starts a conversation by creating a post\n Alice ->> Bob: Add(Create(Note))\n Alice ->> Charlie: Add(Create(Note))\n Note left of Bob: Bob reacts to the post\n Bob ->> Alice: Like(Note)\n Note right of Alice: Alice receives activity and distributes it to other participants\n Alice ->> Charlie: Add(Like(Note))"},{"location":"fep/171b/#container-collection","title":"Container collection","text":"OrderedCollection.attributedTo property containing the id of the conversation owner.collectionOf property with value Activity.The audience of a conversation SHOULD be able to access the container collection. If a server forwards activities without storing them, it is permitted to not publish the collection. However, that would limit the ability of other servers to backfill conversations.
Actors who are not part of the audience MUST be denied access to the collection.
Note
The required collection type might be changed to a more descriptive one (such as ConversationContainer) in a future version of this document. That will allow identification of Add activities belonging to a conversation container by the value of Add.target.type.
Note
In addition to the conversation container collection, implementers MAY provide collection that represents the conversation tree and contains posts.
"},{"location":"fep/171b/#adding-activities-to-a-conversation","title":"Adding activities to a conversation","text":"Conversation owner can add any activity to the conversation. However, if a context property is present on the activity, its value SHOULD be identical to the ID of a conversation container.
When activity is added to the conversation, its owner sends an Add activity to the conversation audience (usually defined by a top-level post).
Add activities published by the conversation owner MUST have a target property containing a partial object:
type: OrderedCollectionid: the id of the conversation container.attributedTo: the id of the collection owner.Add activities published by the conversation owner MUST be added to the conversation container collection.
Note
The \"conversation outbox\" model where the target of Add activity is a collection of Add activities is not compatible with ActivityStreams definition of Add activity, according to which Add activity indicates that the actor has added the object to the target.
The author of a top-level post it not necessarily the conversation owner. When owner is a group, conversations can be started by any of its members.
The top-level post MUST have a contextHistory property that refers to the conversation container. The presence of this property indicates that publisher implements conversation containers.
Note
In addtion to contextHistory property, implementers MAY add a context property that refers to a collection of posts.
Regardless of their audience, all activities in a conversation SHOULD only be delivered to the conversation owner.
Conversation participants SHOULD reject conversation activities that have not been added to the conversation by its owner.
The audience of a reply MUST be copied from a conversation root.
Reply with a different audience can be created by starting a new conversation and including a FEP-e232 link to the post instead of specifying it in inReplyTo.
When conversation owner does not want to add activity to a conversation, that activity is ignored and a corresponding Add activity is not published.
To remove a previously approved post from a conversation, its owner publishes a Delete activity where object is the post that must be removed. This activity is then wrapped in Add activity and distributed to the conversation audience.
Note
Actor shouldn't be able to delete objects it didn't create. In a future version of this document Delete might be replaced with Remove(target: Thread).
Conversation participants can retrieve missing activities by reading the conversation container collection.
"},{"location":"fep/171b/#authentication","title":"Authentication","text":"When an ActivityPub server receives an Add activity in its inbox, it MUST perform the authentication procedure according to FEP-fe34:
Add.object is not embedded, fetch it. If location of the fetched activity has the same origin as Add.object, add it to the conversation.Add.object is embedded, check whether Add.actor and Add.object.actor have the same origin.Add.object to the conversation.Add.object to the conversation.Add.object by its id. If location of the fetched activity has the same origin as Add.object.id, add fetched activity to the conversation.The processing of unauthenticated embedded activities is strongly discouraged. If such activities are not rejected by the consumer, a malicious conversation owner may be able to perform a cache poisoning attack and overwrite any actor or a post in consumer's local cache by sending a forged Update(Actor) or Update(Object) wrapped in an Add activity.
Warning
Sometimes activities have non-dereferenceable identifiers. That may prevent their authentication.
"},{"location":"fep/171b/#examples","title":"Examples","text":"Example of an Add activity for a reply to a followers-only post:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Add\",\n \"id\": \"https://alice.example/activities/add/1\",\n \"actor\": \"https://alice.example/actors/1\",\n \"object\": {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Create\",\n \"id\": \"https://bob.example/activities/create/1\",\n \"actor\": \"https://bob.example/actors/1\",\n \"context\": \"https://alice.example/contexts/1\",\n \"contextHistory\": \"https://alice.example/contexts/1/history\",\n \"object\": {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Note\",\n \"id\": \"https://bob.example/posts/1\",\n \"inReplyTo\": \"https://alice.example/posts/1\",\n \"content\": \"This is a reply\",\n \"to\": [\n \"https://alice.example/actors/1\",\n \"https://alice.example/actors/1/followers\"\n ]\n },\n \"to\": [\n \"https://alice.example/actors/1\",\n \"https://alice.example/actors/1/followers\"\n ]\n },\n \"target\": {\n \"type\": \"OrderedCollection\",\n \"id\": \"https://alice.example/contexts/1/history\",\n \"attributedTo\": \"https://alice.example/actors/1\"\n },\n \"to\": [\n \"https://bob.example/actors/1\",\n \"https://alice.example/actors/1/followers\"\n ]\n}\n Example of a container of a followers-only conversation:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/171b\"\n ],\n \"type\": \"OrderedCollection\",\n \"id\": \"https://alice.example/contexts/1/history\",\n \"attributedTo\": \"https://alice.example/actors/1\",\n \"collectionOf\": \"Activity\",\n \"orderedItems\": [\n \"https://alice.example/activities/add/1\"\n ]\n}\n"},{"location":"fep/171b/#comparison-with-other-proposals","title":"Comparison with other proposals","text":"object of Add is an object, not an activity, and conversation collection contains added objects. Reject(Create) activity is generated for rejected posts. Conversation participants are expected to add a target property to posts.Announce activity is used instead of Add. Conversation and related activities are synchronized between participants, but conversation backfilling mechanism is not specified.Accept or Reject activity is generated for every interaction (in a conversation container Add activity is generated when activity is approved, and rejected activities are ignored). Conversation is not synchronized between participants, but can be backfilled by recursively fetching replies collections.CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/1970/","title":"FEP-1970: Chat Links","text":"Authors Status Date received Tracking issue Discussions Repository John Livingston git@john-livingston.frDRAFT 2023-07-04 #128 - codeberg"},{"location":"fep/1970/#summary","title":"Summary","text":"This FEP describes a way to attach a chat room to ActivityPub actors and objects. The chat room itself can be a web page, a XMPP room, a Matrix room, an IRC channel, ... The chat itself does not necessarily publish messages using ActivityPub.
"},{"location":"fep/1970/#chat-links","title":"Chat links","text":"Chat link is an object with the following properties:
type (REQUIRED): the type MUST be Link.name (RECOMMENDED): the name property SHOULD contain a human-readable description of the chat link.href (REQUIRED): the href property MUST contain a chat URI. This can be an URL of a website, or any other kind of URI, such as a XMPP uri.rel (REQUIRED): the rel property MUST contain the string discussion or an array containing that string. The discussion relation type is proposed as a HTML5 link type extensions.Chat links MUST be added to the attachment array of an actor or an object.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.tld/video/123\",\n \"attributedTo\": \"https://example.tld/users/alice\",\n \"name\": \"Let's go live!\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n }\n ]\n}\n"},{"location":"fep/1970/#basic-actor-attachment","title":"Basic actor attachment","text":"{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Person\",\n \"id\": \"https://example.tld/users/alice\",\n \"inbox\": \"https://example.tld/users/alice/inbox\",\n \"outbox\": \"https://example.tld/users/alice/outbox\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n }\n ]\n}\n"},{"location":"fep/1970/#multiple-way-to-join-the-chat","title":"Multiple way to join the chat","text":"In the following example, the chat room can be joined using a web browser or a xmpp client:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Video\",\n \"id\": \"https://example.tld/video/123\",\n \"attributedTo\": \"https://example.tld/users/alice\",\n \"name\": \"Let's go live!\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n },\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"xmpp://123@room.example.tld?join\",\n \"rel\": \"discussion\"\n }\n ]\n}\n"},{"location":"fep/1970/#live-video-stream","title":"Live video stream","text":"For a live stream, represented by a Video object, the chat link could link to the room where viewers can interract with the streamer.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Video\",\n \"id\": \"https://example.tld/video/123\",\n \"attributedTo\": \"https://example.tld/users/alice\",\n \"name\": \"Let's go live!\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n }\n ]\n}\n If the chat room is unique per streamer (and common to all the streamer videos), it can be attached to both the Video and the Person object.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Person\",\n \"id\": \"https://example.tld/users/alice\",\n \"following\": \"https://example.tld/users/alice/following\",\n \"followers\": \"https://example.tld/users/alice/followers\",\n \"name\": \"Alice\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n }\n ]\n}\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Video\",\n \"id\": \"https://example.tld/video/123\",\n \"attributedTo\": \"https://example.tld/users/alice\",\n \"name\": \"Let's go live!\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n }\n ]\n}\n If the chat room is unique to a streamer's channel, it can be linked to both the Video and the Group object.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Group\",\n \"id\": \"https://example.tld/channel/alice\",\n \"following\": \"https://example.tld/users/alice/following\",\n \"followers\": \"https://example.tld/users/alice/followers\",\n \"name\": \"Alice channel\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n }\n ]\n}\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Video\",\n \"id\": \"https://example.tld/video/123\",\n \"attributedTo\": \"https://example.tld/users/alice\",\n \"name\": \"Let's go live!\",\n \"attachment\": [\n {\n \"type\": \"Link\",\n \"name\": \"Chat\",\n \"href\": \"https://example.tld/chat/room/123\",\n \"rel\": \"discussion\"\n }\n ]\n}\n"},{"location":"fep/1970/#document-discussion","title":"Document discussion","text":"Some file sharing sofwares allows to have a discussion room attached to files. In such case, the chat links can be added as attachment to Document objects.
"},{"location":"fep/1970/#person-instant-messaging","title":"Person instant messaging","text":"If a user has instant messaging applications, chat links could be added to its Person object, so that we can join him.
If the user has multiple instant messaging accounts (XMPP, Matrix, ...), each on of them can be added, and differenciated by the URI scheme.
"},{"location":"fep/1970/#implementations","title":"Implementations","text":"At the time of the writing, this FEP is been implemented in the Peertube livechat plugin, a chat plugin for Peertube.
"},{"location":"fep/1970/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/1985/","title":"FEP-1985: Signaling how an OrderedCollection is ordered","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT 2024-10-10 #417 Discussions codeberg"},{"location":"fep/1985/#summary","title":"Summary","text":"OrderedCollection is defined as an ordered set in the Activity Vocabulary, but the precise ordering is not defined. The ActivityPub specification requires that instances of OrderedCollection MUST be ordered reverse chronologically by insertion order, but a later errata was proposed to relax this restriction by only applying it to properties defined as OrderedCollection within the ActivityPub specification. Consequently, this allows for some collections to be presented forward chronologically by insertion order, and some collections to be presented reverse chronologically by insertion order. This FEP introduces an orderType property and two vocabulary terms ForwardChronological and ReverseChronological to explicitly signal the ordering of a collection.
https://w3id.org/fep/1985/orderType Label is ordered in Comment Indicates the type of ordering for an OrderedCollection. Domain OrderedCollection Range OrderingClass (@vocab) Required No Functional Yes See also ForwardChronological | ReverseChronological Is defined by FEP-1985 \n\n{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/1985\"\n ],\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/1\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/3\"\n ],\n \"orderType\": \"ForwardChronological\"\n}\n\n \n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/1\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/3\"\n ],\n \"https://w3id.org/fep/1985/orderType\": {\n \"id\": \"https://w3id.org/fep/1985/ForwardChronological\"\n }\n}\n\n OrderingClass URI https://w3id.org/fep/1985/OrderingClass Label OrderingClass Comment Abstract base class for the orderType property's range. See also ForwardChronological | ReverseChronological Is defined by FEP-1985 ForwardChronological URI https://w3id.org/fep/1985/ForwardChronological Label forward chronological order Comment This OrderedCollection is ordered forward chronologically, and adding an item to this collection will add it at the end. Subclass of OrderingClass See also orderType Is defined by FEP-1985 ReverseChronological URI https://w3id.org/fep/1985/ReverseChronological Label reverse chronological order Comment This OrderedCollection is ordered reverse chronologically, and adding an item to this collection will add it at the start. Subclass of OrderingClass See also orderType Is defined by FEP-1985"},{"location":"fep/1985/#examples","title":"Examples","text":"(This section is non-normative.)
Example of a forward chronological OrderedCollection with additional context:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/1985\"\n ],\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/1\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/3\"\n ],\n \"orderType\": \"ForwardChronological\"\n}\n Example of a forward chronological OrderedCollection without additional context:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/1\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/3\"\n ],\n \"https://w3id.org/fep/1985/orderType\": {\n \"id\": \"https://w3id.org/fep/1985/ForwardChronological\"\n }\n}\n Example of a reverse chronological OrderedCollection with additional context:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/1985\"\n ],\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/3\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/1\"\n ],\n \"orderType\": \"ReverseChronological\"\n}\n Example of a reverse chronological OrderedCollection without additional context:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/3\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/1\"\n ],\n \"https://w3id.org/fep/1985/orderType\": {\n \"id\": \"https://w3id.org/fep/1985/ReverseChronological\"\n }\n}\n"},{"location":"fep/1985/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/1985/ForwardChronological/","title":"ForwardChronological","text":"URI : https://w3id.org/fep/1985/ForwardChronological
Label : forward chronological order
Comment : This OrderedCollection is ordered forward chronologically, and adding an item to this collection will add it at the end.
Subclass of : OrderingClass
See also : orderType
Is defined by : FEP-1985
"},{"location":"fep/1985/OrderingClass/","title":"OrderingClass","text":"URI : https://w3id.org/fep/1985/OrderingClass
Label : OrderingClass
Comment : Abstract base class for the orderType property's range.
See also : ForwardChronological | ReverseChronological
Is defined by : FEP-1985
"},{"location":"fep/1985/ReverseChronological/","title":"ReverseChronological","text":"URI : https://w3id.org/fep/1985/ReverseChronological
Label : reverse chronological order
Comment : This OrderedCollection is ordered reverse chronologically, and adding an item to this collection will add it at the start.
Subclass of : OrderingClass
See also : orderType
Is defined by : FEP-1985
"},{"location":"fep/1985/orderType/","title":"orderType","text":"URI : https://w3id.org/fep/1985/orderType
Label : is ordered in
Comment : Indicates the type of ordering for an OrderedCollection.
Domain : OrderedCollection
Range : OrderingClass (@vocab)
Required : No
Functional : Yes
See also : ForwardChronological | ReverseChronological
Is defined by : FEP-1985
"},{"location":"fep/1985/orderType/#examples","title":"Examples","text":"Example of a forward chronological OrderedCollection with additional context
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/1985\"\n ],\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/1\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/3\"\n ],\n \"orderType\": \"ForwardChronological\"\n}\n Example of a forward chronological OrderedCollection without additional context
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-collection\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n \"https://domain.example/objects/1\",\n \"https://domain.example/objects/2\",\n \"https://domain.example/objects/3\"\n ],\n \"https://w3id.org/fep/1985/orderType\": {\n \"id\": \"https://w3id.org/fep/1985/ForwardChronological\"\n }\n}\n"},{"location":"fep/19b3/","title":"FEP-19b3: Specifying Properties of a Service","text":"Authors Status Date received Tracking issue Discussions Repository Helge DRAFT 2025-11-04 #719 Discussions codeberg"},{"location":"fep/19b3/#summary","title":"Summary","text":"Actors of type Service are used in the Fediverse to represent automated process. In this FEP, we suggest some property values to use to convey further information about the underlying automated process and the responsible parties for the automated process.
Property values can be attached to an actor to specify additional values. Using property values has two key advantage:
The official definition of PropertyValue can be found at PropertyValue - Schema.org Type. The section Examples of Property Value contains examples and further discussions on the usage of property value.
The property values suggested here are meant to provide information to the users of the Fediverse. They are not meant to influence the behavior of Fediverse applications.
"},{"location":"fep/19b3/#source","title":"Source","text":"This property value should provide a link to the source code, e.g.
{\n \"type\": \"PropertyValue\",\n \"name\": \"Source\",\n \"value\": \"https://codeberg.org/helge/release_helper\"\n}\n"},{"location":"fep/19b3/#author","title":"Author","text":"The Author field would specify the author of the source linked in Source. The exact format of the value can be debated. Some possibilities
@helge@mymath.rocks,acct:helge@mymath.rocksuser@domain.example or mailto:user@domain.example)If the author of the service is not the one running it, one needs to use an alternative field. We suggest using Support in this case.
"},{"location":"fep/19b3/#frequency","title":"Frequency","text":"One can run services with a periodic trigger, e.g. cron. This indicates their frequency. Example:
{\n \"type\": \"PropertyValue\",\n \"name\": \"Frequency\",\n \"value\": \"At 42 minutes past the hour\"\n}\n"},{"location":"fep/19b3/#servicetype","title":"ServiceType","text":"Indicates the type of the service, e.g. Comment Tracking System, see FEP-136c. This should be primarily used for services that do more complicated processing, and link to documentation what the service does.
{\n \"type\": \"PropertyValue\",\n \"name\": \"ServiceType\",\n \"value\": \"Comment Tracking System, https://bovine.codeberg.page/comments/136c/fep-136c/\"\n}\n We imagine that specifying Reacts To Mentions would be useful to indicate that this is a bot the answers when mentioned. Furthermore, for bots just as @release, once might specify something like On Event, as they are triggered when continuous integration jobs are run.
"},{"location":"fep/19b3/#examples-of-property-value","title":"Examples of property value","text":"The following example is taken from the actor below
{\n \"type\": \"PropertyValue\",\n \"name\": \"Source\",\n \"value\": \"https://codeberg.org/helge/release_helper\"\n}\n In difference to the example provided by us, mastodon uses HTML as the value of property values, e.g.
{\n \"type\": \"PropertyValue\",\n \"name\": \"\ud83e\udd16\",\n \"value\": \"<a href=\\\"https://botwiki.org\\\" target=\\\"_blank\\\" rel=\\\"nofollow noopener me\\\" translate=\\\"no\\\"><span class=\\\"invisible\\\">https://</span><span class=\\\"\\\">botwiki.org</span><span class=\\\"invisible\\\"></span></a>\"\n}\n This example also shows another behavior, we recommend against: using emojis as name. This has the disadvantage of being awkward to parse (in particular for people using screen readers).
"},{"location":"fep/19b3/#example-of-actor-with-property-value","title":"Example of actor with property value","text":"An example of an actor with property values looks like
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"PropertyValue\": {\n \"@id\": \"https://schema.org/PropertyValue\",\n \"@context\": {\n \"value\": \"https://schema.org/value\",\n \"name\": \"https://schema.org/name\"\n }\n }\n }\n ],\n \"id\": \"https://dev.bovine.social/actor/kH3y9kw8cqRUgzso3oM3_w\",\n \"type\": \"Service\",\n \"preferredUsername\": \"release\",\n \"name\": \"release\",\n \"attachment\": [\n {\n \"type\": \"PropertyValue\",\n \"name\": \"Author\",\n \"value\": \"acct:helge@mymath.rocks\"\n },\n {\n \"type\": \"PropertyValue\",\n \"name\": \"Source\",\n \"value\": \"https://codeberg.org/helge/release_helper\"\n }\n ], \n \"...\": \"...\"\n}\n Note
There are variance with @context which change which URIs property values expand if one uses JSON-LD. As JSON-LD is unused for all practical purposes, this currently does not matter. See the Appendix for details on what goes wrong.
The document
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"PropertyValue\": {\n \"@id\": \"https://schema.org/PropertyValue\",\n \"@context\": {\n \"value\": \"https://schema.org/value\",\n \"name\": \"https://schema.org/name\"\n }\n }\n }\n ],\n \"name\": \"release\",\n \"attachment\": [\n {\n \"type\": \"PropertyValue\",\n \"name\": \"Author\",\n \"value\": \"acct:helge@mymath.rocks\"\n }\n ]\n}\n turns to
{\n \"https://www.w3.org/ns/activitystreams#attachment\": {\n \"@type\": \"https://schema.org/PropertyValue\",\n \"https://schema.org/name\": \"Author\",\n \"https://schema.org/value\": \"acct:helge@mymath.rocks\"\n },\n \"https://www.w3.org/ns/activitystreams#name\": \"release\"\n}\n when compacted against \"@context\": {}. Any variation of @context should behave similarly. The obvious choices
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://schema.org\"\n ],\n \"name\": \"release\",\n \"attachment\": [\n {\n \"type\": \"PropertyValue\",\n \"name\": \"Author\",\n \"value\": \"acct:helge@mymath.rocks\"\n }\n ]\n}\n and with the order of \"https://www.w3.org/ns/activitystreams\" and \"https://schema.org\" reversed lead to incorrect results due to name being overloaded.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/1b12/","title":"FEP-1b12: Group federation","text":"Authors Status Date received Date final Tracking issue Discussions Repository Felix Ableitner me@nutomic.comFINAL 2022-11-12 2023-02-09 #22 - codeberg"},{"location":"fep/1b12/#summary","title":"Summary","text":"Internet forums are one of the oldest forms of social media. This document describes how they are implemented in existing Activitypub platforms using Group actors. It also introduces a new property to indicate that a given object belongs to a group.
Friendica released federated forums in version 2019.03.
Lemmy published the first public beta of federated groups in v0.8.0 (October 2020).
[FEP-400e] introduces publicly appendable collections, which can also be used to implement forums. However they are incompatible with the implementations described here.
"},{"location":"fep/1b12/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this specification are to be interpreted as described in [RFC-2119].
"},{"location":"fep/1b12/#group-actor","title":"Group actor","text":"A federated forum is represented by a Group actor as specified in [Activity Vocabulary]. This actor is not directly controlled by a human, but can perform its main functionality in a fully automated way, essentially acting as a bot. It has the same general properties as any Person actor.
Public groups SHOULD support a standard Follow-Accept workflow. This ensures compatibility with existing implementations which support Person following using the same vocabulary. After receiving a valid Follow activity, the group SHOULD automatically respond with an Accept/Follow, and add the sender to its followers collection. Correspondingly actors can unfollow a group by sending an Undo/Follow activity.
In order to render content in a forum, it is necessary to know which particular forum the content belongs to. This way users can navigate up from a thread to the forum's main page, or view metadata like the description or moderators. With current implementations there is no easy way to determine which forum a given activity or object belongs to, if any.
Currently there are different approaches to specify which group a given object or activity belongs to. Lemmy, Friendica and lotide put the group ID in the to field. Peertube uses attributedTo. Both properties have the problem that they are also used for different purposes by some platforms, and are represented as arrays. So to retrieve the group from a received object, an implementation needs to loop through these properties and resolve each URL, until it hits one which resolves to a Group. Clearly this is very inefficient.
To simplify this process, we propose to specify the group identifier in the audience property. This property is part of Activitystreams, but not yet used in the wild. This way no extension is necessary, and neither will it cause problems for existing implementations. Additionally, platforms can continue to federate the group identifier in the existing format for backwards compatibility.
Each Group actor represents a single forum. Forums contain many user-submitted threads, which can be represented by different objects depending on the implementation. Thread objects SHOULD have a name property which represents the thread title.
Example thread:
{\n \"type\": \"Page\",\n \"id\": \"https://sally.example.org/p/1\",\n \"attributedTo\": \"https://sally.example.org\",\n \"name\": \"Hello forum!\",\n \"audience\": \"https://example.org/my-forum\"\n}\n Threads can have replies, which are usually represented as Notes. They MUST have a property inReplyTo referencing either the thread they belong to, or the parent reply in case of nested replies.
Example reply:
{\n \"type\": \"Note\",\n \"id\": \"https://sally.example.org/p/3\",\n \"attributedTo\": \"https://sally.example.org\",\n \"inReplyTo\": \"https://sally.example.org/p/1\",\n \"content\": \"My first comment\",\n \"audience\": \"https://example.org/my-forum\"\n}\n The properties inReplyTo and audience can be used to navigate up from a reply to a thread and forum. To navigate down from a group to threads and comments, groups MAY have a replies collection which contains all threads. Each thread MAY again have a replies collection which lists all top-level comments responding to the thread.
The main task of a group is to distribute content among its followers.
When a group receives a activity in its inbox, it SHOULD perform some automatic validation, such as checking for domain and user blocks. Groups MAY require additional validation, such as accepting content only from followers, or even manual approval from group moderators. In case an activity fails these checks, the group MAY respond to the sender with a Reject activity.
In case the incoming activity is deemed valid, the group MUST wrap it in an Announce activity, with the original activity as object. The wrapped activity MUST be preserved exactly as it was received, without changing or removing any properties. This ensures that forwarded activities can be verified with [Object Integrity Proofs]. Announce activities SHOULD get added to the group outbox. If the group exposes collections of threads and comments, relevant items should also be added to them.
Example:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.org/my-forum\",\n \"id\": \"example.org/a/5\",\n \"object\": {\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Create\",\n \"id\": \"https://sally.example.org/a/2\"\n \"actor\": \"https://sally.example.org\",\n \"audience\": \"https://example.org/my-forum\",\n \"object\": {\n \"type\": \"Page\",\n \"id\": \"https://sally.example.org/p/1\",\n \"content\": \"Hello forum!\",\n \"audience\": \"https://example.org/my-forum\"\n }\n }\n}\n After the group successfully verifies and wraps the received activity, it sends it to the inboxes of its followers. Followers then use the outer Announce activity to verify that the content was really approved by the group. After this step the Announce can be discarded and only the inner activity shown to users.
This mechanism can be used to publish any possible activity type. Examples include Announce/Like, Announce/Delete/Note or Announce/Undo/Like. Implementations may choose not to forward some activity types which are considered private, for example Follow activities.
Group moderators are those actors who control the group, are able to change its metadata and remove malicious content. They are listed in the group\u2019s attributedTo collection. Moderation is an optional feature, implementations can safely ignore this entire section. At the moment it is only implemented by Lemmy.
{\n \"id\": \"https://example.org/my-forum\",\n \"type\": \"Group\",\n \"name\": \"Ten Forward\",\n \"attributedTo\": \"https://example.org/my-forum/moderators\",\n}\n {\n \"type\": \"OrderedCollection\",\n \"id\": \"https://example.org/my-forum/moderators\",\n \"orderedItems\": [\n \"https://example.org/picard\",\n \"https://example.org/riker\"\n ]\n}\n Group moderators can be changed with Add and Remove activities:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.org/my-forum\",\n \"id\": \"example.org/a/8\",\n \"object\": {\n \"id\": \"https://example.org/a/7\",\n \"type\": \"Add\",\n \"actor\": \"https://example.org/picard\",\n \"object\": \"http://example.org/data\",\n \"audience\": \"https://example.org/my-forum\",\n \"target\": \"https://example.org/my-forum/moderators\"\n }\n}\n The actions which can be done by moderators are called moderation activities. These are implementation specific, examples include Add, Remove (to change the moderators collection), Block (ban malicious users) and Update/Group (change group metadata).
If an group or group follower supports moderation, it MUST validate incoming moderation activities before further processing. Such activities MUST have an actor who is listed in attributedTo. Group followers MUST additionally verify that the moderation activity was announced by the group.
Implementations SHOULD also accept moderation activities which come from the same server where the community is hosted, under the assumption that these are sent by server administrators. These moderation activities also need to be wrapped in Announce by the group.
This document is written based on existing group implementations in Lemmy, Friendica, Hubzilla, Lotide and Peertube. These already federate successfully in production.
The audience field is an exception as it is not in use yet. Lemmy will add support for it in version 0.17.0.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/2100/","title":"Index","text":"hide: - navigation description: \"Historically, after the sudden death of a popular instance, one could neither target groups hosted at it anymore nor contact the whole followers collection to let them know of the new instance housing a certain group. If we always have absolute knowledge of the complete followers collection (or good enough), we can automate based on which instance has more local followers which server would become the new house. Another alternative would be to automatically archive the old group and start again from scratch. This FEP, on the other hand, discusses something very different of automatically moving an actor from one server to a different one. It is about collaboration between different group or organization actors to promote an unified experience between the participants of the linked group actors. We think this may be easier, more flexible, and promote a better UX than only notifying the actor that the house of a certain group has moved, but both solutions would probably achieve similar results in the above use case. This proposal introduces an interpretation of a Group following another Group and the gs unbound attribute. This allow two groups (or organization) to \"act as one\" (not exactly, but elaborated afterwards). This primarily aims at effectively removing a central point of authority for groups, but offers more than that. With this, @alice@undefinedhackers.net can mention a group named hackers (!hackers) or even address an activity To !hackers@instance.gnusocial.test (C2S) and let her instance's !hackers announce to other instances' !hackers. Finally, this proposal is general enough to allow a server to simultaneously have !lug@server (without links), !lug-unbound@server (with the greatest links collection it can grow), and !lug-with-some-links@server (with only some links). It doesn't require linked groups to have the same preferredUsername.\"
"},{"location":"fep/2100/#fep-2100-unbound-group-and-organization","title":"FEP-2100: Unbound Group and Organization","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Diogo Peralta Cordeiro mail@diogo.siteWITHDRAWN 2022-03-31 2025-04-06 #36 Discussions codeberg This FEP wasn't a result of my individual work but rather of the joint effort in this SocialHub discussion and, prior to that, the discussion in GNU social's IRC/XMPP with rozzin (Joshua Judson Rosen) and someonewithpc (Hugo Sales).
"},{"location":"fep/2100/#summary","title":"Summary","text":"Historically, after the sudden death of a popular instance, one could neither target groups hosted at it anymore nor contact the whole followers collection to let them know of the new instance housing a certain group. If we always have absolute knowledge of the complete followers collection (or good enough), we can automate based on which instance has more local followers which server would become the new house. Another alternative would be to automatically archive the old group and start again from scratch.
This FEP, on the other hand, discusses something very different of automatically moving an actor from one server to a different one. It is about collaboration between different group or organization actors to promote an unified experience between the participants of the linked group actors. We think this may be easier, more flexible, and promote a better UX than only notifying the actor that the house of a certain group has moved, but both solutions would probably achieve similar results in the above use case.
This proposal introduces an interpretation of a Group following another Group and the gs:unbound attribute. This allow two groups (or organization) to \"act as one\" (not exactly, but elaborated afterwards).
This primarily aims at effectively removing a central point of authority for groups, but offers more than that. With this, @alice@undefinedhackers.net can mention a group named hackers (!hackers) or even address an activity To !hackers@instance.gnusocial.test (C2S) and let her instance's !hackers announce to other instances' !hackers.
Finally, this proposal is general enough to allow a server to simultaneously have !lug@server (without links), !lug-unbound@server (with the greatest links collection it can grow), and !lug-with-some-links@server (with only some links). It doesn't require linked groups to have the same preferredUsername.
To keep things simple, sometimes you will see things formatted like Activity{Object}. For example, Create{Note} would be a Create activity containing a Note in the object field. Also, we will focus in Actor of type Group, but nothing should stop from using this for Organization.
@nickname@server will be used to refer Actors of type Person or Application.!nickname@server will be used to refer Actors of type Group or Organization.@#!group@server#collection will be used to refer collection collection of !group@server.The key words MAY, MUST, MUST NOT, SHOULD, and SHOULD NOT are to be interpreted as described in [RFC2119].
"},{"location":"fep/2100/#links-between-groups-terminology","title":"Links between Groups terminology","text":""},{"location":"fep/2100/#activitystreams-20-requirements-for-this-mechanism","title":"ActivityStreams 2.0 requirements for this mechanism","text":""},{"location":"fep/2100/#example-group-actor-in-this-fep","title":"Example Group Actor in this FEP","text":"{\n \"type\": \"Group\",\n \"streams\": [],\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"gs\": \"https://www.gnu.org/software/social/ns#\"\n },\n {\n \"unbound\": {\n \"@id\": \"gs:unbound\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"id\": \"https://instance.gnusocial.test/group/hackers\",\n \"unbound\": true,\n \"preferredUsername\": \"hackers\",\n \"endpoints\": {\n \"sharedInbox\": \"https://instance.gnusocial.test/inbox.json\"\n },\n \"inbox\": \"https://instance.gnusocial.test/group/hackers/inbox.json\",\n \"outbox\": \"https://instance.gnusocial.test/group/hackers/outbox.json\",\n \"following\": \"https://instance.gnusocial.test/group/hackers/subscriptions\",\n \"followers\": \"https://instance.gnusocial.test/group/hackers/subscribers\",\n}\n"},{"location":"fep/2100/#creating-a-link-between-two-group-actors","title":"Creating a link between two group actors","text":"Creating a directed link between two group actors is just a regular Follow request between any two actors.
Assume that !hackers@instance.gnusocial.test sends a Follow request to !lug@gnusocial.net.
If gs:unbound: false or not present, then if !lug@gnusocial.net accepts the Follow request, it will Announce{*} entering its inbox to !hackers@instance.gnusocial.test.
If gs:unbound: true, then !lug@gnusocial.net will both accept the Follow request and submit a Follow request of its own to !hackers@instance.gnusocial.test.
If both !hackers@instance.gnusocial.test and !lug@gnusocial.net have added each other to their linksTo, they will act as if they were the same group. If they have equivalent groupLinks collections, then they are essentially fully mirrored groups.
Note that the \"Link negotiation\" happens between two Group actors (S2S).
"},{"location":"fep/2100/#some-scenarios","title":"Some scenarios","text":""},{"location":"fep/2100/#1-group-a-follows-group-b-which-has-gsunbound-false","title":"1. Group A follows Group B which hasgs:unbound = false","text":"gs:unbound = true","text":"gs:unbound = true.gs:unbound attribute","text":"!hackers@C: Announce{Note} TO !hackers@[B] (S2S)!hackers@C as well.CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/2277/","title":"FEP-2277: ActivityPub core types","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT informational 2025-01-31 #485 Discussions codeberg"},{"location":"fep/2277/#summary","title":"Summary","text":"Classification of ActivityPub objects based on their shape.
"},{"location":"fep/2277/#rationale","title":"Rationale","text":"ActivityPub applications often have different processing rules for actors, activities, collections and other objects. In most cases, the class of an object can be inferred from its context: object delivered to inbox is expected to be an activity, and the value of its actor property is expected to be an actor.
However, the class can not always be inferred from context. In some cases, only the object ID is known, such as when it is provided by a user. Embeddings may also be ambiguous:
object of Update activity can be an object or an actor.object of Announce activity can be an object or an activity.Applications may use the type property to determine object's class, but that hinders interoperabilty because such applications would not be able to process objects with unknown types. Therefore, a different approach would be preferable.
Activity Streams 2.0 standard defines 8 core object types:
ObjectLinkActivityIntransitiveActivityCollectionOrderedCollectionCollectionPageOrderedCollectionPageUnfortunately, definitions provided in the specification are not precise. Only Object and Link are defined as disjoint types, meaning an object could be an Activity and a Collection at the same time. \"Actors\" are described as specializations of Object, but there is no corresponding Actor core type.
The lack of good definitions and the exclusion of the Actor type make standard classification unsuitable for practical purposes. Therefore, applications may need to use a different classification.
One way to divide objects into distinct classes is to look at their properties and their connections to other objects (indicated by their properties). This approach can be used to define 7 core types:
Actor: an entity that publishes and receives activities.Activity: an action taken by an actor.Collection: a container for other objects (a collection or a collection page).VerificationMethod: a verification method.PublicKey: a public key (a legacy form of a verification method).Link: a link.Object: all other objects.The next section specifies an algorithm that classifies any ActivityPub object as one of these core types by analyzing the object's shape. This technique is often referred to as duck typing.
"},{"location":"fep/2277/#duck-typing","title":"Duck typing","text":"The following algorithm can be used to determine the core type of the object:
inbox and outbox properties, return Actor.publicKeyMultibase property, return VerificationMethod.publicKeyPem property, return PublicKey.href property, return Link.actor property, return Activity.items, orderedItems, totalItems, partOf, first, last, next, prev or current property, return Collection.Object.Application of this algorithm results in non-overlapping core types. For example, an actor with items property is still an actor and not a collection.
The value of type property is not taken into consideration.
Warning
ActivityPub standard requires actors to have both inbox and outbox properties, but in practice outbox is not always present. If compatibility with non-conformant implementations is desirable, step #1 can be changed to \"If object has inbox property, return Actor\".
Warning
Pleroma adds an actor property to objects that are not activities. To make an allowance for that, the step #5 of the algorithm can be changed to \"If object has an actor property, and doesn't have an attributedTo property, return Activity\".
The output of the algorithm might be different in LD-aware and in LD-unaware applications due to a possibility of re-mapping of terms in @context.
This may pose a security risk.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"foo\": \"as:inbox\",\n \"bar\": \"as:outbox\"\n }\n ],\n \"type\": \"Note\",\n \"id\": \"https://social.example/note\",\n \"foo\": \"https://social.example/inbox\",\n \"bar\": \"https://social.example/outbox\"\n}\n"},{"location":"fep/2277/#alternatives-considered","title":"Alternatives considered","text":""},{"location":"fep/2277/#multi-typing","title":"Multi-typing","text":"The alternative to duck typing is to use multiple types. For example, this object can be unambiguously identified as an Activity:
{\n \"type\": [\"Bite\", \"Activity\"]\n}\n However, existing implementations don't add a second type, and even if changing all of them were possible, duck typing would still need to be used as a fallback during the transitional period.
"},{"location":"fep/2277/#type-hierachies","title":"Type hierachies","text":"The core type of an object can be determined via the definition of its type in a vocabulary, but that means all ActivityPub applications would have to support JSON-LD.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/22b6/","title":"FEP-22b6: Linking an ActivityPub Object to a HTML page and back","text":"Authors Status Date received Tracking issue Discussions Repository Helge @helge@mymath.rocksDRAFT 2025-11-12 #730 Discussions codeberg"},{"location":"fep/22b6/#summary","title":"Summary","text":"Links are a fundamental part of the internet. This FEP describes how to use links to link a HTML page to an ActivityPub object. The mechanisms described in this document are not new and are used to link to RSS feeds (see alternate, second example).
"},{"location":"fep/22b6/#linking-from-a-html-page","title":"Linking from a HTML page","text":"The HTML living standard states about alternate
The keyword creates a hyperlink referencing an alternate representation of the current document.
In this sense, we can link from the HTML page to an ActivityPub object using
<head>\n ...\n <link rel=\"alternate\" type=\"application/activity+json\" href=\"http://location.example/object.id\">\n ...\n</head>\n which can be used for automatic discovery. To provide a link for the user, one should use an anchor, e.g.
<body>\n ...\n <a rel=\"alternate\" type=\"application/activity+json\" href=\"http://location.example/object.id\">\n ActivityPub Object\n </a>\n ...\n</body>\n We recommend using both forms as they serve different purposes.
Warning
One might be tempted to use canonical instead of alternate if the HTML page just renders the content of the ActivityPub object. The author believes that sticking to the single format giving here has the highest chance of leading to widespread support.
"},{"location":"fep/22b6/#discussions","title":"Discussions","text":"As the example below shows, there are use cases when the ActivityPub object and the page are on different servers. One can however still check the relationship, by verifying the ActivityPub object contains a link back to the page.
Similarly, the ActivityPub object might provide less content than the webpage.
"},{"location":"fep/22b6/#example","title":"Example","text":"The page https://bovine.codeberg.page/comments/ contains the link element
<link rel=\"alternate\"\n href=\"https://comments.bovine.social/pages/aHR0cHM6Ly9ib3ZpbmUuY29kZWJlcmcucGFnZS9jb21tZW50cy8=\"\n type=\"application/activity+json\">\n and the linked object resolves to
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Page\",\n \"attributedTo\": \"https://comments.bovine.social/actor/rF4xnx1QraAIU3Krg-2Qzg\",\n \"to\": [\n \"https://www.w3.org/ns/activitystreams#Public\"\n ],\n \"published\": \"2025-10-20T17:45:11Z\",\n \"url\": [\n {\n \"type\": \"Link\",\n \"href\": \"https://bovine.codeberg.page/comments/\",\n \"mediaType\": \"text/html\"\n }\n ],\n \"name\": \"Comment Tracking System\",\n \"summary\": null,\n \"id\": \"https://comments.bovine.social/pages/aHR0cHM6Ly9ib3ZpbmUuY29kZWJlcmcucGFnZS9jb21tZW50cy8=\",\n \"context\": \"https://comments.bovine.social/pages/aHR0cHM6Ly9ib3ZpbmUuY29kZWJlcmcucGFnZS9jb21tZW50cy8=/context\",\n \"replies\": \"https://comments.bovine.social/pages/aHR0cHM6Ly9ib3ZpbmUuY29kZWJlcmcucGFnZS9jb21tZW50cy8=/replies\",\n \"likes\": \"https://comments.bovine.social/pages/aHR0cHM6Ly9ib3ZpbmUuY29kZWJlcmcucGFnZS9jb21tZW50cy8=/likes\",\n \"shares\": \"https://comments.bovine.social/pages/aHR0cHM6Ly9ib3ZpbmUuY29kZWJlcmcucGFnZS9jb21tZW50cy8=/shares\"\n}\n The Page object can be understood as Page in the ActivityVocabulary here, i.e. \"Represents a Web Page\".
"},{"location":"fep/22b6/#alternatives","title":"Alternatives","text":"As already said, we recommend using this suggested approach to link from the HTML to an ActivityPub object.
However, there are other methods that are in use to provide the link, and should be considered valid options.
For the use case of FEP-136c: Comment Tracking Services, that motivates this work, embedding the links in the HTML is the only viable option. This is due to the HTML often being hosted on static sites that do not provide the option to use Content Negotiation or Link Headers.
"},{"location":"fep/22b6/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/2677/","title":"FEP-2677: Identifying the Application Actor","text":"Authors Status Date received Tracking issue Discussions Repository Helge helge.krueger@gmail.comDRAFT 2023-10-14 #190 - codeberg"},{"location":"fep/2677/#summary","title":"Summary","text":"It is a common pattern in Fediverse applications to have a special actor of type Application. This is for example the actor at https://mastodon.example/actor for Mastodon or at https://pleroma.example/internal/fetch for Pleroma. This application actor can be fetched with an unsigned request, so it is possible to use it to fetch public keys.
The goal of this FEP is to provide an explicit mechanism of identifying the application actor, with the goal of making it usable for further tasks, e.g.
We will first define, what we mean by an application actor. We will impose two requirements on them
As already mentioned application actors are currently used by most Fediverse applications to fetch public keys. See here for a list of examples.
"},{"location":"fep/2677/#identifying-the-application-actor-with-nodeinfo","title":"Identifying the Application Actor with nodeinfo","text":"In [NodeInfo], see also [FEP-f1d5], the well-known path /.well-known/nodeinfo is defined and it is specified that a document in JRD format [RFC 7033] is served there.
The requirement of the current FEP is that the /.well-known/nodeinfo contains an additional link with relation type https://www.w3.org/ns/activitystreams#Application, which resolves to an application actor as described in the previous section.
We note that it is not necessary to implement the relations specified in [NodeInfo] to satisfy the current FEP.
"},{"location":"fep/2677/#example","title":"Example","text":"We will consider the server with domain node.example. This means that a request to https://node.example/.well-known/nodeinfo will resolve to
{\n \"links\": [\n {\n \"rel\": \"http://nodeinfo.diaspora.software/ns/schema/2.0\",\n \"href\": \"https://node.example/nodeinfo/2.0\"\n },\n {\n \"rel\": \"https://www.w3.org/ns/activitystreams#Application\",\n \"href\": \"https://node.example/actor\"\n }\n ]\n }\n Next, a request to https://node.example/actor with accept header application/activity+json could return
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/v1\",\n ],\n \"id\": \"https://node.example/actor\",\n \"type\": \"Application\",\n \"inbox\": \"https://node.example/actor/inbox\",\n \"outbox\": \"https://node.example/actor/outbox\",\n \"publicKey\": {\n \"id\": \"https://node.example/actor#main-key\",\n \"owner\": \"https://node.example/actor\",\n \"publicKeyPem\": \"-----BEGIN PUBLIC KEY-----\\n....\\n-----END PUBLIC KEY-----\\n\"\n }\n}\n"},{"location":"fep/2677/#discussion","title":"Discussion","text":"The approach of this FEP has been chosen to have a light touch. Some alternative choices would be:
Both options have the downside that it would require to introduce a new path, which forces every implementation to implement it in a similar way. Instead, we reuse an existing path, and the implementer is free to choose where to put the application actor.
The second question is why fix the type Application and not Service. First, it is consistent with most current implementations. Second, Mastodon uses Service for bot accounts. So it feels like a good idea to start distinguishing from this usage. We would phrase this distinction as follows:
Application is triggered by events in the application, e.g. a signed request and then fetching the corresponding public key.Service is triggered by either activities arriving at its inbox or external events, e.g. a timer. This means an actor of type Service is similar to one controlled by an user.These are not hard rules on when to use Application or Service. They will probably break down as more complicated Fediverse implementations are build. We hope that they can serve as a level of guidance, how to differentiate actors.
https://bovine.example/activitypub/bovine Firefish https://firefish.example/actor Lemmy https://lemmy.example/ Mastodon https://mastodon.example/actor Mitra http://mitra.example/actor Pleroma https://pleroma.example/internal/fetch Mbin https://mbin.example/i/actor WordPress https://wordpress.example/wp-json/activitypub/1.0/application Mobilizon https://mobilizon.example/relay Gancio https://gancio.example/federation/u/<instance_name> Friendica https://friendica.example/ PeerTube https://peertube.example/accounts/peertube Pixelfed https://pixelfed.example/i/actor Note: Feel free to add further links.
"},{"location":"fep/2677/#implementations","title":"Implementations","text":"Software dateImplemented dateReleased WordPress 2023-12-21 - Mobilizon 2023-12-14 - Gancio 2023-12-22 -"},{"location":"fep/2677/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/268d/","title":"FEP-268d: Search consent signals for objects","text":"Authors Status Date received Tracking issue Discussions Repository Daiki \"tesaguri\" Mizukami tesaguriguma+fep-268d@gmail.comDRAFT 2024-10-12 #421 Discussions codeberg"},{"location":"fep/268d/#summary","title":"Summary","text":"This FEP documents an extension property for [Activity Streams 2.0] to signal the consent for an object to be searched by a given actor.
"},{"location":"fep/268d/#history","title":"History","text":"This section is non-normative.
Fedibird introduced the searchableBy property in August 2022.
kmyblue implemented the searchableBy property in April 2023.
Mastodon began implementing the indexable property [FEP-5feb] in February 2023 and released it in September 2023.
This section is non-normative.
[FEP-5feb] introduced the indexable property, which allows an Activity Streams actor to signal their preference for whether objects attributed to them should be made publicly available for search.
The indexable property is an actor-level property which takes a boolean value, allowing the actor to make all of their objects available for search by either anyone or no one at all. However, sometimes it is desirable to make a certain set of objects searchable/unsearchable or make an object searchable by a limited audience. The object-level searchableBy property documented in this FEP complements the indexable property with a more fine-grained audience targeting ability.
The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"NOT RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in [RFC 2119].
"},{"location":"fep/268d/#terms-defined","title":"Terms defined","text":"searchableBy URIhttp://fedibird.com/ns#searchableBy Label searchable by Comment Identifies one or more entities that are allowed to search for the subject. Domain Object Range Object | Link Required No Is defined by FEP-268d A non-normative example of a publicly searchable post: {\n \"@context\": [\n \"https://w3id.org/fep/268d\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"id\": \"https://example.com/notes/1\",\n \"attributedTo\": \"https://example.com/users/1\",\n \"to\": \"https://www.w3.org/ns/activitystreams#Public\",\n \"cc\": \"https://example.com/users/1/followers\",\n \"type\": \"Note\",\n \"content\": \"Hello, world!\",\n \"searchableBy\": \"https://www.w3.org/ns/activitystreams#Public\"\n}\n A post only searchable by the mentioned users and the author's followers: {\n \"@context\": [\n \"https://w3id.org/fep/268d\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"id\": \"https://example.com/notes/42\",\n \"attributedTo\": \"https://example.com/users/1\",\n \"to\": \"https://alice.example/actor\",\n \"cc\": [\"https://example.com/users/1/followers\", \"https://www.w3.org/ns/activitystreams#Public\"],\n \"type\": \"Note\",\n \"content\": \"@Alice Happy birthday!\",\n \"tag\": {\n \"type\": \"Mention\",\n \"href\": \"https://alice.example/actor\",\n \"name\": \"@Alice\"\n },\n \"searchableBy\": [\"https://alice.example/actor\", \"https://example.com/users/1/followers\"]\n}\n (where `https://example.com/users/1/followers` is the `followers` collection of `https://example.com/users/1`.) A post only searchable by the author themselves: {\n \"@context\": [\n \"https://w3id.org/fep/268d\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"id\": \"https://example.com/notes/123\",\n \"attributedTo\": \"https://example.com/users/1\",\n \"to\": \"https://example.com/users/1/followers\",\n \"cc\": \"https://www.w3.org/ns/activitystreams#Public\",\n \"type\": \"Note\",\n \"summary\": \"Note to self\",\n \"searchableBy\": \"https://example.com/users/1\"\n}\n An actor with a default searchability of `as:Public`: {\n \"@context\": [\n \"https://w3id.org/fep/268d\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"id\": \"https://example.com/users/1\",\n \"type\": \"Person\",\n \"searchableBy\": \"https://www.w3.org/ns/activitystreams#Public\"\n}\n ## Searchability of objects This section describes how the searchability of an object for a given actor should be determined. If the object has at least one `searchableBy` property value and the searching actor isn't included in the property values, the object MUST NOT be made available for search by that actor. Even in this case, the object SHOULD be made available for search if the object is attributed to the searching actor themselves, and MAY be made available for search if the searching actor has previously interacted with the object (e.g. by `Like`-ing the object). If the `searchableBy` property values of the object include the searching actor, the object SHOULD be made available for search by the actor, unless the searching actor is otherwise forbidden to see the object by access controls such as the Activity Streams audience targeting (`to`, `bto`, `cc`, `bcc` and `audience` properties) of the object and `Block`s by the object's attributed actors. Objects with a `searchableBy` value of `https://www.w3.org/ns/activitystreams#Public` SHOULD be made available for search by any users. If the object does not have any `searchableBy` values but is attributed to an actor with at least one `searchableBy` value, the object SHALL be treated as inheriting the `searchableBy` value of the attributed actor. Note that, in JSON-LD, an empty array value (`[]`) is semantically equivalent to `null` or \"undefined\". In consequence, if a publisher wishes to make an object searchable by no one (instead of falling back on the attributed actor's `searchableBy` value), a placeholder object needs to be used as the `searchableBy` value of that object. The RECOMMENDED placeholder in that case is the object's `attributedTo` value, if any. If the object does not fall under any of the conditions described in this section, the searchability of the object MAY be determined by an implementation-defined strategy. Note that the searchability signaled by the `searchableBy` property is only applicable to Activity Streams representation of objects. Searchability of non-Activity Streams representations (most notably, HTML) of objects with non-public `searchableBy` property value SHOULD be signaled by other means. Non-normative examples of such means include the Robots Exclusion Protocol [RFC-9309] [ROBOTSTXT], the `` HTML tag [ROBOTSTXT-META] and the `X-Robots-Tag` HTTP header [GOOGLE-ROBOTS]. ## Interaction with FEP-5feb (`toot:indexable`) *This section is only applicable to conformant [FEP-5feb] implementations.* If an object does not have any `searchableBy` values and is attributed to an actor with no `searchableBy` values but with an `http://joinmastodon.org/ns#indexable` value, the searchability of the object SHOULD be determined according to [FEP-5feb]. If an object has at least one `searchableBy` value, the property SHALL take precedence over the `indexable` property. In particular, if an object is not searchable according to the `searchableBy` property, the object must not be made available for search, even if the object is targeted to the `as:Public` collection and the object is attributed to an actor with `\"indexable\": true`. ## Security considerations *This section is non-normative.* As mentioned in the normative section, consuming implementations should take care to ignore JSON entries with an empty array value (`\"searchableBy\": []`). Failure to do so would have a security implication if the implementation is verifying RDF-based signatures of objects, because the empty array value does not affect the RDF dataset (and hence the resulting signature value), so that an attacker can remove/insert a `\"searchableBy\": []` entry from/to a signed object without making the signature verification fail, allowing them to tamper with the object's searchability if the empty array value were handled differently from \"undefined\". ## Implementations *This section is non-normative.* - [Fedibird](https://github.com/fedibird/mastodon/tree/fedibird) - [kmyblue](https://github.com/kmycode/mastodon) ## References - [Activity Streams 2.0] James M Snell, Evan Prodromou, [Activity Streams 2.0](https://www.w3.org/TR/activitystreams-core/), 2017 - [FEP-5feb] Claire, [FEP-5feb: Search indexing consent for actors](https://w3id.org/fep/5feb), 2023 - [RFC-2119] S. Bradner, [Key words for use in RFCs to Indicate Requirement Levels](https://doi.org/10.17487/RFC2119), 1997 - [RFC-9309] M. Koster, [Robots Exclusion Protocol](https://doi.org/10.17487/RFC9309), 2022 - [ROBOTSTXT] Martijn Koster, [A Standard for Robot Exclusion](https://www.robotstxt.org/orig.html), 1994 - [ROBOTSTXT-META] Martijn Koster, [About the Robots \\ tag](https://www.robotstxt.org/meta.html), 2007 - [GOOGLE-ROBOTS] Google, [Robots Meta Tags Specifications](https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#xrobotstag) ## Copyright CC0 1.0 Universal (CC0 1.0) Public Domain Dedication To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work."},{"location":"fep/2931/","title":"Index","text":"hide: - navigation description: \"FEP-7888 attempts to lay out clarifications for the use of the context property based on rationale and history, in which context is used primarily to logically group objects related by their \"context\", or in other words, stating that some object \"was created in relation to\" another object, where the latter object denotes some purpose for the first object. In response to FEP-7888, and motivated by the desire to backfill entire conversations, various softwares wishing to federate have chosen to directly represent context as a Collection of objects acknowledged to be \"within\" some canonical context collection. This FEP describes this approach, its usages, and some drawbacks.\"
"},{"location":"fep/2931/#fep-2931-representing-context-with-a-collection","title":"FEP-2931: Representing context with a Collection","text":"Authors Status Type Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT implementation 2025-03-22 #534 Discussions codeberg"},{"location":"fep/2931/#summary","title":"Summary","text":"FEP-7888 attempts to lay out clarifications for the use of the context property based on rationale and history, in which context is used primarily to logically group objects related by their \"context\", or in other words, stating that some object \"was created in relation to\" another object, where the latter object denotes some purpose for the first object.
In response to FEP-7888, and motivated by the desire to backfill entire conversations, various softwares wishing to federate have chosen to directly represent context as a Collection of objects acknowledged to be \"within\" some canonical context collection. This FEP describes this approach, its usages, and some drawbacks.
FEP-7888 describes the concept of \"Canonical collections of objects within an authoritative context\" as a case where, if a context dereferences to some object, then the type of that resolved object can indicate a certain relation as representing the \"canonical collection\". In the case of this FEP, the \"canonical collection\" is the Collection itself. This gives us the following normative description:
context property is dereferenceable to an object that is a Collection or OrderedCollection, the canonical context collection is id.(This section is non-normative.)
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://posts.example/1\",\n \"type\": \"Note\",\n \"context\": {\n \"id\": \"https://conversation.example\",\n \"type\": \"OrderedCollection\",\n \"items\": [\n \"https://posts.example/1\",\n \"https://posts.example/2\",\n \"https://posts.example/5\"\n ]\n }\n}\n"},{"location":"fep/2931/#consuming-a-context-collection","title":"Consuming a context Collection","text":"(This section is non-normative.)
The context represented by a Collection can be processed (for example, for backfill purposes) by iteratively processing its items (or orderedItems if expressed using that JSON-LD term) and considering them to be logically grouped together.
(This section is non-normative.)
The protocol for becoming aware of or discovering objects claiming to be included within your context are out of scope for this FEP. However, if you encounter an object claiming your context, then it can be Added directly to the Collection.
"},{"location":"fep/2931/#drawbacks","title":"Drawbacks","text":"(This section is non-normative.)
"},{"location":"fep/2931/#collections-cannot-be-cleanly-followed-via-c2s-outbox-delivery","title":"Collections cannot be cleanly followed via C2S outbox delivery","text":"Using a Collection type forcibly opts that object into certain spec behaviors defined by ActivityPub. Namely, if you are using a context collection, and you wish to make your Collection followable, the naive approach is to make it into an ActivityPub actor by giving it an inbox that can at least respond to Follow activities with an Accept Follow. However, there is an issue with the outbox delivery algorithm defined in the ActivityPub client-to-server API, wherein the inbox discovery algorithm necessarily always attempts to iterate over every single item in the Collection, not just the Collection itself. This behavior is triggered whenever an ActivityPub outbox receives a payload with an addressing property such as to, cc, bto, bcc, or audience. For a context Collection that is also an AP actor, addressing this Collection can be unintentionally costly to the server performing delivery, as this server is expected to attempt iteration over potentially unbounded numerous items from the Collection.
For example, given the following activity:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://activity.example\",\n \"type\": \"Announce\",\n \"object\": \"https://posts.example/7\",\n \"to\": \"https://conversation.example\"\n}\n The intention might be to only POST to the inbox of https://conversation.example (and thus notify the context Collection of https://posts.example/7 existing), but the outbox delivery algorithm as currently defined at the time of writing this FEP will instead attempt delivery to all of the following inbox properties it can find across the following objects:
https://conversation.examplehttps://posts.example/1https://posts.example/2https://posts.example/5There is no way to opt out of this \"Collection expansion\" behavior. Consequently, following a context Collection cannot be done via naive ActivityPub. See FEP-efda: Followable objects for a potential workaround, where the Follow activity is addressed to attributedTo and sent to attributedTo.inbox instead of being addressed directly to id and delivered to id.inbox.
This problem does not occur if the ActivityPub \"POST to outbox\" is not used, and instead the Follow activity is delivered directly with Linked Data Notifications \"POST to inbox\".
When modeling explicit concepts such as a \"conversation\", there is a difference between saying that \"a conversation is a collection of posts\", versus saying that \"a conversation has a collection of posts\". The former relation (\"is a\") may be imprecise or may prevent declaring other relations in a way that makes sense. The latter relation (\"has a\") allows for declaring properties of the Conversation that are not properties of the collection of posts instead.
"},{"location":"fep/2931/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/2c59/","title":"FEP-2c59: Discovery of a Webfinger address from an ActivityPub actor","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@prodromou.nameDRAFT 2024-01-04 #226 - codeberg"},{"location":"fep/2c59/#summary","title":"Summary","text":"[Webfinger][Webfinger] is used on the fediverse to abstract out variations in [ActivityPub][ActivityPub] actor URL formats, giving a uniform way of addressing an actor. With a Webfinger address, a client can discover the actor's ActivityPub actor URL. This specification defines an explicit way to reverse the process, and discover a preferred Webfinger address from an ActivityPub actor URL.
"},{"location":"fep/2c59/#motivation","title":"Motivation","text":"[ActivityPub][ActivityPub] is a protocol for federated social networking. It uses HTTPS URLs to identify actors, that is, the people, organizations, applications and groups that use the protocol.
The URL format is not standardized, and there are many variations. For example, a user with the username evan on the activitypub.example domain could have an actor URL of:
https://activitypub.example/users/evanhttps://activitypub.example/users/1234https://social.activitypub.example/evanhttps://evan.activitypub.example[Webfinger][Webfinger] is a discovery protocol for people on the Internet. A Webfinger address is an email-address-like identity in the form user@activitypub.example. Following the Webfinger protocol, a client can discover information about the person at that address, including their ActivityPub actor URL.
This discovery is unidirectional and can cross domain boundaries. This allows potentially misleading or harmful use of WebFinger to identify actors on the network without their consent. For example, the owner of idiot.example could create a Webfinger address pompous@idiot.example that points to https://activitypub.example/users/evan.
In addition, ActivityPub servers that display actors' Webfinger addresses in their UIs need a reliable way to know which Webfinger is preferred for a given actor ID.
To solve this problem, [Mastodon][Mastodon] constructs a canonical Webfinger address for an actor by extracting the preferredUsername from the actor's profile, and prepending it to the domain name of the actor's identity URL. It then does a Webfinger lookup on that address, and if the actor URL returned matches the identity URL, it uses that Webfinger address.
This technique can cause issues for certain actor URLs. First, preferredUsername is not a required property for an ActivityPub actor.
Second, it becomes inconvenient to use subdomains or alternate domains for actor URLs. If the original WebFinger is evan@organization.example, and the ActivityPub URL is https://social.organization.example/evan, the Webfinger will be canonicalized to the longer evan@social.organization.example. A similar problem arises if the actor URL uses one subdomain per user, like https://evan.organization.example/.
This specification defines a way to discover a Webfinger address from an ActivityPub actor without constructing it from properties and URL parsing. It gives domain owners the option to use a registered domain for their Webfinger addresses, and host their ActivityPub server on a subdomain or a different domain. The Mastodon method can be used as a fallback.
"},{"location":"fep/2c59/#specification","title":"Specification","text":"webfinger property, which is a canonical Webfinger address for the actor.webfinger property is functional, that is, there can be at most one webfinger property per actor.webfinger property MUST link directly to the actor URL verbatim, without redirects or aliases.webfinger property SHOULD be a plain Webfinger address, like user@domain.example.webfinger property MAY be an acct: URL, like acct:user@domain.example.The context document for this specification is https://purl.archive.org/socialweb/webfinger. Its contents are as follows:
{\n \"@context\": {\n \"wf\": \"https://purl.archive.org/socialweb/webfinger#\",\n \"xsd\": \"http://www.w3.org/2001/XMLSchema#\",\n \"webfinger\": {\n \"@id\": \"wf:webfinger\",\n \"@type\": \"xsd:string\"\n }\n }\n}\n"},{"location":"fep/2c59/#examples","title":"Examples","text":"A publisher can include the webfinger property for an actor.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/webfinger\"\n ],\n \"id\": \"https://social.example.com/evanp\",\n \"type\": \"Person\",\n \"name\": \"Evan Prodromou\",\n \"inbox\": \"https://social.example.com/evanp/inbox\",\n \"outbox\": \"https://social.example.com/evanp/outbox\",\n \"following\": \"https://social.example.com/evanp/following\",\n \"followers\": \"https://social.example.com/evanp/followers\",\n \"liked\": \"https://social.example.com/evanp/liked\",\n \"webfinger\": \"evanp@example.com\"\n}\n A publisher can include the webfinger property for a Person object referenced in the actor property of an activity.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/webfinger\"\n ],\n \"id\": \"https://social.example.com/evanp/activity/775\",\n \"to\": [\"Public\"],\n \"type\": \"Like\",\n \"summary\": \"Evan liked a note\",\n \"actor\": {\n \"type\": \"Person\",\n \"name\": \"Evan Prodromou\",\n \"id\": \"https://social.example.com/evanp\",\n \"webfinger\": \"evanp@example.com\"\n },\n \"object\": {\n \"type\": \"Note\",\n \"id\": \"https://social.example.com/other/note/221\",\n \"to\": [\"Public\"]\n }\n}\n The publisher can include the webfinger property for an actor with an acct: prefix.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/webfinger\"\n ],\n \"id\": \"https://prefix.example/urlfan\",\n \"type\": \"Person\",\n \"name\": \"URL Fan\",\n \"inbox\": \"https://prefix.example/urlfan/inbox\",\n \"outbox\": \"https://prefix.example/urlfan/outbox\",\n \"following\": \"https://prefix.example/urlfan/following\",\n \"followers\": \"https://prefix.example/urlfan/followers\",\n \"liked\": \"https://prefix.example/urlfan/liked\",\n \"webfinger\": \"acct:urlfan@prefix.example\"\n}\n"},{"location":"fep/2c59/#security-considerations","title":"Security Considerations","text":"The source of the webfinger property is important. A third party may spoof, intentionally or unintentionally, the webfinger property of another actor. The property should only be treated as the preferred Webfinger address for the actor if it comes from the actor id URL, or if it is delivered with the actor's [HTTP Signature][HTTP Signature] via the ActivityPub protocol.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/2e40/","title":"FEP-2e40: The FEP Vocabulary Extension Process","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Helge Krueger helge.krueger@gmail.comWITHDRAWN 2023-02-13 2023-09-04 #62 - codeberg"},{"location":"fep/2e40/#summary","title":"Summary","text":"Current usage of ActivityPub relies on the ActivityStreams namespace [AS-NS] combined with custom extensions [Mastodon NS]. As far as I can tell, no best practices exist or a formal process to add new namespaces.
This FEP will
Note: Withdrawn as no longer compatible with the FEP repository structure, see this issue.
"},{"location":"fep/2e40/#background-and-terminology","title":"Background and Terminology","text":"The JSON-LD context is introduced in 3.1 The Context of [JSON-LD]. The context of an object is specified by its @context property.
One can think of the context as defining certain strings to be equivalent. For example Note, as:Note, and https://www.w3.org/ns/activitystreams#Note all represent the same thing. More details can be found in 3.2. IRIs. Following [JSON-LD], we will refer to all three strings mentioned above as a Term. The focus here is on having a format that is machine-readable. Having a standardized format allows various algorithms to be applicable see [Canon] and [LD-API].
The second useful aspect of this is that one can define the used terms through the provided URL: https://www.w3.org/ns/activitystreams#Note. Clicking on it will let you easily find the definiton of the Note Type. This format is human-readable.
We will refer to the combination of Context and easily accessible documentations for the terms a Vocabulary.
"},{"location":"fep/2e40/#requirements","title":"Requirements","text":"The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].
"},{"location":"fep/2e40/#the-fep-vocabulary","title":"The FEP Vocabulary","text":"We now formalize the process of creating an adding to the FEP Vocabulary. This section formalizes the location of the two files containing the machine readable json-ld context and human readable markdown description. How to add new terms will be discussed in the next section.
This FEP creates the file /feps/assets/fep-2e40/namespace.json with content
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"fep\": \"https://w3id.org/fep#\"\n }\n ]\n}\n A description of this context is contained in /feps/assets/fep-2e40/namespace.md. The goal of inclusion of the ActivityStreams context is to increase the chances of most objects having the simple form
{\n \"@context\": \"https://w3id.org/fep\",\n ...\n}\n which is as close as one can get to pure JSON with JSON-LD. These two files form the FEP Vocabulary. Through a pull-request to [W3-ID], these two files will be made available at https://w3id.org/fep.
The authors of a new FEP-xxxx following [FEP-a3ed] MAY include a section or sections titled \"Create FEP Term: $TERM\". The $TERM MUST follow the following rules inspired by points 2-5 of 3. The Registration Process in [DID-Reg].
Furthermore, the author MAY perform the following action
fep-xxxx-$TERM to /feps/assets/fep-2e40/namespace.json./feps/assets/fep-2e40/namespace.md.The changes to the two files MUST be described in the FEP, and they MUST only add new content or change content added by the FEP. An example is provided in the next section. While these actions are optional at the DRAFT stage of a FEP, they MUST be performed before a FEP can reach its FINAL stage. We will refer to a term introduced by this process as a FEP term.
Instead of having many sections with similar names, the author of a FEP MAY combine them.
Instead of defining a new FEP term, the author of a FEP MAY decide to include terms from another namespace as a FEP Term. For example, we decide to introduce conversation as follows
\"fep-xxxx-conversation\": {\n \"@id\": \"http://ostatus.org#conversation\",\n \"@type\": \"@id\"\n },\n This has the advantage that terms, which are currently used but not properly documented, can be given a proper definition. Furthermore, this will help keep the @context block small. When doing so, the author MUST either provide a reference to the current definition of the object OR write a new definition of the object. If a reference is provided, it should be considered best pratice to quote the reference.
In [FEP-61ce], I plan to introduce the term eventSource. As this will most likely be the first FEP using the herein defined procces, this will lead to /feps/assets/fep-2e40/namespace.json being changed to
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"fep\": \"https://www.w3id.org/fep#\",\n \"fep-61ce-eventSource\": {\n \"@id\": \"fep:fep-61ce-eventSource\",\n \"@type\": \"@id\"\n }\n }\n ]\n}\n with usage information
The term eventSource is to be as part of the endpoints of an [ActivityPub] Actor. It specifies an endpoint, where the Client can receive push notifications using the Server Side Events protocol of activities being added to collections on the server. By default the inbox collection of the Actor is used. By specifying the X-ActivityPub-Collection header a different collection can be specified to retrieve push notifications from.
Example usage:
{\n \"@context\": [\"https://www.w3id.org/fep\",\n {\"eventSource\": \"fep-61ce-eventSource\"}\n ],\n \"type\": \"Person\",\n \"id\": \"https://example.com/client_actor\",\n \"inbox\": \"https://example.com/client_actor/inbox\",\n \"outbox\": \"https://example.com/client_actor/outbox\",\n \"preferredUsername\": \"actor\",\n \"endpoints\": {\n \"proxyUrl\": \"https://example.com/client_actor/proxyUrl\",\n \"eventSource\": \"https://example.com/client_actor/eventSource\"\n }\n}\n "},{"location":"fep/2e40/#promotion-to-common-terms","title":"Promotion to common terms","text":"While the above example is already nice looking, it still has the uglyness of needing to define eventSource as fep-61ce/eventSource. We will call the process of adding the line
\"eventSource\": \"fep-61ce-eventSource\"\n to /feps/assets/fep-2e40/namespace.json a promotion to common term.
In order to be elligible for promotion, a term MUST NOT conflict with any term currently in the vocabulary. For example, if the term visualization was already introduced in a previous FEP, it is not acceptable to introduce visualisation and expect promotion of this term.
Any term elligible for promotion CAN be promoted to a common term by the author of the FEP after the FEP has been declared FINAL. If an author does not seek to promotion of a term, it SHOULD be noted in the section introducing this term.
"},{"location":"fep/2e40/#secondary-fep-vocabularies","title":"Secondary FEP Vocabularies","text":"We have now introduced a process for the common or primary vocabulary with context in /feps/assets/fep-2e40/namespace.json and documentation in /feps/assets/fep-2e40/namespace.json. We will now introduce secondary vocabularies. These are meant to be an option to introduce many domain specific terms without having to add them to the primary vocabulary.
An author of a FEP MAY include a section titled \"Introducing Secondary FEP Vocabulary fep-DOMAIN\". Here DOMAIN is a short name representing the domain. Then files /feps/fep-xxxx/namespace.json and /feps/fep-xxxx/namespace.md can be created. An example of a file of the @context file is given below. By including the original fep context, one can keep duplication to a minimum. Similarly, the documentation file should only explain the namespace and which additional terms are promoted or overridden. For the documentation of the fep terms, one should refer back to the original documentation.
Furthermore, the author MAY adjust the configuration of [W3-ID] to reflect his changes. All obligations that are MAY during the draft stage will turn into MUST before the FEP can be finalized.
"},{"location":"fep/2e40/#example-the-barber-vocabulary","title":"Example: The barber vocabulary","text":"The barber community in the FediVerse has decided that, they need a marker if people shave themself. So they have introduce FEP-ba1b and defined the term shavesHimself. It was decided not to promote this term to the primary vocabulary. Instead the barber community has decided to introduce FEP-ba2b, in which they introduce the secondary FEP vocabulary fep-barber. The context of this vocabulary then takes the form:
{\n \"@context\": [\n \"https://www.w3id.org/fep\",\n {\n \"shavesHimself\": \"fep-ba1b-shavesHimself\"\n }\n ]\n}\n By including the primary FEP vocabulary, it is ensured that changes to the primary vocabulary carry over to the secondary fep-barber vocabulary. Furthermore, by the promotion of shavesHimself taking place after the inclusion of the primary vocabulary, it is ensure that the primary vocabulary cannot override it.
The comments by aschrijver and trwnh on SocialHub have helped me greatly in clarifying and formulating the concepts discussed in this FEP.
"},{"location":"fep/2e40/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/2e40/namespace/","title":"The FEP Vocabulary","text":"This file contains the description of Terms of the FEP Vocabulary. The FEP Vocabulary is defined according to [FEP-2e40]. The FEP Vocabulary is an extension of Activity Streams 2.0, which is described in [AS-NS].
"},{"location":"fep/2e40/namespace/#promoted-terms","title":"Promoted terms","text":"The following terms have been elevated to be usable without their fep prefix.
"},{"location":"fep/2e40/namespace/#definitions-of-terms","title":"Definitions of Terms","text":"This section contains the definition of terms separated by FEP.
"},{"location":"fep/2e40/namespace/#references","title":"References","text":"DRAFT 2024-01-31 #255 - codeberg"},{"location":"fep/3264/#summary","title":"Summary","text":"This document describes an implementation of project planning and work coordination based on ActivityPub protocol and Valueflows vocabulary. It includes planning what people want to do, and (optionally) recording what is done.
"},{"location":"fep/3264/#history","title":"History","text":"This is a parallel submission to FEP-c5a1 To-do's, which supports much simpler planning. Both planning submissions continue the idea of Valueflows extensions to ActivityPub/ActivityStreams based on use case, started by silverpill with FEP-0837 Federated Marketplace.
"},{"location":"fep/3264/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/3264/#planning-a-project-or-some-work","title":"Planning a project or some work","text":"This FEP will support work plans or projects of any size, using a Plan with one or more Processes, each with one or more input and/or output Commitments.
Consuming implementations which don't have planning features MAY display plan-related objects similarly to Note objects.
In all cases, the objects can also be discussed using Note objects and replyTo.
Valueflows defines a Plan as a logical collection of processes that constitute a body of scheduled work with defined deliverable(s).
The representation of a plan is a JSON document with the following properties:
id (REQUIRED): the plan's unique global identifier.type (REQUIRED): the type of the object SHOULD be Plan. If interoperability with other ActivityPub services is desirable, implementers MAY also use object types from Activity Vocabulary, such as Note.attributedTo (REQUIRED): the actor who published the plan.name (REQUIRED): the title of the plan.content (OPTIONAL): the description of the plan. The type of content SHOULD be text/html.published (RECOMMENDED): the date and time at which the plan was published.to (REQUIRED): the audience of the plan.planIncludes (REQUIRED): the processes that are part of this plan (see below).A Process is defined as An activity that changes inputs into outputs, by transforming or transporting economic resource(s). Processes that are part of a plan can be part of a series (graph) of processes that produce the final output(s) at the end; and/or they can be unrelated to each other, and produce separate final output(s) for the plan.
The representation of a process is a JSON document with the following properties:
id (REQUIRED): the process's unique global identifier.type (REQUIRED): the type of the object MUST be Process.name (REQUIRED): an informal or formal textual identifier for a process.hasBeginning (OPTIONAL): the date and time the process is planned to begin.hasEnd (OPTIONAL): the date and time the process is planned to end.content (OPTIONAL): the description of what is to be done in the process. The type of content SHOULD be text/html.hasInput (OPTIONAL): the input commitment(s) of the process (see below).hasOutput (OPTIONAL): the output commitment(s) of the process (see below).A Commitment is defined as A planned economic flow that has been promised by an agent to another agent.
id (REQUIRED): the commitment's unique global identifier.type (REQUIRED): the type of the object MUST be Commitment.action (REQUIRED): the type of economic transaction. If the commitment is an input, the value of this property SHOULD be one of: work, use, consume, cite, pickup, accept. It the commitment is an output, the value of this property SHOULD be one of: produce, deliverService, dropoff, modify. See the action definitions and behaviors for more information.resourceConformsTo (RECOMMENDED): the type of an economic resource (can be a skill or type of work for the work action). Could be any URI.resourceQuantity (OPTIONAL): the amount and unit of the non-effort-based economic resource. Either resourceQuantity or effortQuantity is RECOMMENDED, and both can be included in case of use. This is an object with two properties:hasUnit (REQUIRED): name of the unit, according to Ontology of units of Measure classification.hasNumericalValue (REQUIRED): amount of the resource.effortQuantity (OPTIONAL): the amount and unit of the work or use effort-based action. This is an object with two properties:hasUnit (REQUIRED): name of the unit, according to Ontology of units of Measure classification.hasNumericalValue (REQUIRED): amount of the effort.provider (RECOMMENDED): the actor who commits to providing the resource. If not included, it is assumed to be the attributedTo actor.receiver (RECOMMENDED): the actor who commits to receiving the resource. If not included, it is assumed to be the to actor.content (OPTIONAL): the description of what is to be done for this commitment. The type of content SHOULD be text/html.Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Plan\": \"vf:Plan\",\n \"Process\": \"vf:Process\",\n \"Commitment\": \"vf:Commitment\",\n \"receiver\": \"vf:receiver\",\n \"provider\": \"vf:provider\",\n \"action\": \"vf:action\",\n \"resourceConformsTo\": \"vf:resourceConformsTo\",\n \"resourceQuantity\": \"vf:resourceQuantity\",\n \"effortQuantity\": \"vf:effortQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\",\n \"planIncludes\": \"vf:planIncludes\",\n \"hasBeginning\": \"vf:hasBeginning\",\n \"hasEnd\": \"vf:hasEnd\",\n \"hasInput\": \"vf:hasInput\",\n \"hasOutput\": \"vf:hasOutput\"\n }\n ],\n \"type\": \"Plan\",\n \"id\": \"https://project.example/plans/ddde9d6f-6f3b-4770-a966-3a18ef006931\",\n \"attributedTo\": \"https://project.example/actors/alice\",\n \"name\": \"Making Apple Cider\",\n \"content\": \"Annual weekend of community apple cider making\",\n \"published\": \"2024-09-18T19:22:03.918737Z\",\n \"to\": \"https://project.example/actors/gleaning-collective\",\n \"planIncludes\": [\n {\n \"type\": \"Process\",\n \"id\": \"https://project.example/processes/ddde9d6f-6f3b-4770-a966-3a18ef006931#pick\",\n \"name\": \"Pick Apples\",\n \"hasBeginning\": \"2024-10-18T09:00:00.000000Z\",\n \"hasEnd\": \"2024-10-18T18:00:00.000000Z\",\n \"content\": \"Meet at the orchard, bring your own basket and a lunch, and be ready for some outdoor fall work and fun.\",\n \"hasInput\": [\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#pick1\",\n \"action\": \"work\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q112729805\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"6\"\n },\n \"provider\": \"https://project.example/actors/bob\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\"\n },\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#pick2\",\n \"action\": \"work\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q112729805\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"6\"\n },\n \"provider\": \"https://project.example/actors/chiam\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\"\n },\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#pick3\",\n \"action\": \"work\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q112729805\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"6\"\n },\n \"provider\": \"https://project.example/actors/alice\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\"\n }\n ],\n \"hasOutput\": [\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/outputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#pick4\",\n \"action\": \"produce\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q89\",\n \"resourceQuantity\": {\n \"hasUnit\": \"pound\",\n \"hasNumericalValue\": \"500\"\n },\n \"provider\": \"https://project.example/actors/gleaning-collective\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\"\n }\n ]\n },\n {\n \"type\": \"Process\",\n \"id\": \"https://project.example/processes/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider\",\n \"name\": \"Make Cider\",\n \"hasBeginning\": \"2024-10-19T09:00:00.000000Z\",\n \"hasEnd\": \"2024-10-19T16:00:00.000000Z\",\n \"content\": \"Meet at the community building, be prepared to get messy. We will press the apples, then clean the press.\",\n \"hasInput\": [\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider1\",\n \"action\": \"consume\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q89\",\n \"resourceQuantity\": {\n \"hasUnit\": \"pound\",\n \"hasNumericalValue\": \"500\"\n },\n \"provider\": \"https://project.example/actors/gleaning-collective\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\",\n \"content\": \"The apples picked the day before.\"\n },\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider2\",\n \"action\": \"use\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q674484\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"7\"\n },\n \"provider\": \"https://project.example/actors/diego\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\",\n \"content\": \"Diego will drop his cider press off early in the morning, and pick it up later, preferably nice and clean.\"\n },\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider3\",\n \"action\": \"work\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q3485549\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"4\"\n },\n \"provider\": \"https://project.example/actors/chiam\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\"\n },\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider4\",\n \"action\": \"work\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q3485549\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"6\"\n },\n \"provider\": \"https://project.example/actors/bob\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\"\n }\n ],\n \"hasOutput\": [\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/outputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider5\",\n \"action\": \"produce\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q5977438\",\n \"resourceQuantity\": {\n \"hasUnit\": \"gallon-US\",\n \"hasNumericalValue\": \"15\"\n },\n \"provider\": \"https://project.example/actors/gleaning-collective\",\n \"receiver\": \"https://project.example/actors/food-pantry\"\n },\n {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/outputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider6\",\n \"action\": \"produce\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q2836947\",\n \"resourceQuantity\": {\n \"hasUnit\": \"pound\",\n \"hasNumericalValue\": \"100\"\n },\n \"provider\": \"https://project.example/actors/gleaning-collective\",\n \"receiver\": \"https://project.example/actors/singing-trees-farm\",\n \"content\": \"The apple remains will be great pig food, leave it in the big bin, and they will pick it up.\"\n }\n ]\n }\n ]\n}\n"},{"location":"fep/3264/#accepting-a-commitment","title":"Accepting a Commitment","text":"The object of Accept activity MUST be the id of the Commitment in the Plan object previously sent to the actor.
Activity MAY contain content property for further coordination.
Example:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Accept\",\n \"id\": \"https://project.example/activities/059f08fa-31b1-4136-8d76-5987d705a0ac\",\n \"actor\": \"https://project.example/actors/bob\",\n \"object\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider4\",\n \"content\": \"I'll come early to help unload the cider press.\",\n \"to\": \"https://project.example/actors/alice\"\n}\n"},{"location":"fep/3264/#rejecting-a-commitment","title":"Rejecting a Commitment","text":"The object of Reject activity MUST be the id of the Commitment in the Plan object previously sent to the actor.
Activity MAY contain content property indicating the reason for rejection.
Example:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Reject\",\n \"id\": \"https://project.example/activities/8c05f97f-1531-4b70-9ca8-4ee4a09f36a4\",\n \"actor\": \"https://project.example/actors/chiam\",\n \"object\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider3\",\n \"content\": \"Sorry, already working elsewhere that day, will check with Eleanore.\",\n \"to\": \"https://project.example/actors/alice\"\n}\n"},{"location":"fep/3264/#adding-a-commitment-to-the-plan","title":"Adding a Commitment to the Plan","text":"In some cases, an actor might want to add a new commitment to the plan.
The representation of a commitment is a JSON document with the following properties:
id (REQUIRED): the commitment's unique global identifier.type (REQUIRED): the type of the object SHOULD be Commitment. If interoperability with other ActivityPub services is desirable, implementers MAY also use object types from Activity Vocabulary, such as Note.attributedTo (REQUIRED): the actor who published the commitment.content (OPTIONAL): the description of the commitment or communication about the commitment. The type of content SHOULD be text/html.published (RECOMMENDED): the date and time at which the commitment was published.to (REQUIRED): the audience of the commitment.action (REQUIRED): the type of economic transaction. If the commitment is an input, the value of this property SHOULD be one of: work, use, consume, cite, pickup, accept. It the commitment is an output, the value of this property SHOULD be one of: produce, deliverService, dropoff, modify. See the action definitions and behaviors for more information.inputOf: the process for which this commitment is an input. Either inputOf or outputOf is REQUIRED.outputOf: the process for which this commitment is an output. Either inputOf or outputOf is REQUIRED.context (OPTIONAL): the plan the commitment is part of.resourceConformsTo (RECOMMENDED): the type of an economic resource (can be a skill or type of work for the work action). Could be any URI.resourceQuantity: the amount and unit of the non-effort-based economic resource. Either resourceQuantity or effortQuantity is RECOMMENDED, and both can be included in case of use. This is an object with two properties:hasUnit (REQUIRED): name of the unit, according to Ontology of units of Measure classification.hasNumericalValue (REQUIRED): amount of the resource.effortQuantity: the amount and unit of the work or use effort-based action. Either resourceQuantity or effortQuantity is RECOMMENDED, and both can be included in case of use. This is an object with two properties:hasUnit (REQUIRED): name of the unit, according to Ontology of units of Measure classification.hasNumericalValue (REQUIRED): amount of the resource.provider (RECOMMENDED): the actor who commits to providing the resource. If not included, it is assumed to be the attributedTo actor.receiver (RECOMMENDED): the actor who commits to receiving the resource. If not included, it is assumed to be the to actor.{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Commitment\": \"vf:Commitment\",\n \"inputOf\": \"vf:inputOf\",\n \"receiver\": \"vf:receiver\",\n \"provider\": \"vf:provider\",\n \"action\": \"vf:action\",\n \"resourceConformsTo\": \"vf:resourceConformsTo\",\n \"effortQuantity\": \"vf:effortQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\"\n }\n ],\n \"type\": \"Create\",\n \"id\": \"https://project.example/activities/ad2f7ee1-6567-413e-a10b-72650cbdc743/create\",\n \"actor\": \"https://project.example/actors/eleanore\",\n \"object\": {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/inputs/ad2f7ee1-6567-413e-a10b-72650cbdc743\",\n \"attributedTo\": \"https://project.example/actors/eleanore\",\n \"context\": \"https://project.example/plans/ddde9d6f-6f3b-4770-a966-3a18ef006931\",\n \"published\": \"2024-09-21T14:13:41.843794Z\",\n \"inputOf\": \"https://project.example/processes/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider\",\n \"action\": \"work\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q3485549\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"5\"\n },\n \"provider\": \"https://project.example/actors/eleanore\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\"\n },\n \"to\": \"https://project.example/actors/alice\"\n}\n"},{"location":"fep/3264/#recording-what-actually-was-done","title":"Recording what actually was done","text":"Actual economic activity is represented with EconomicEvents in Valueflows. Economic events can be recorded as fulfilling a planned commitment, or by themselves if they were not explicitly planned. In either case, in the scope of this FEP, they are also inputs or outputs of a process.
This FEP does not include creating and updating of actual resource inventory, which is also effected through economic events.
The representation of an economic event is a JSON document with the following properties:
id (REQUIRED): the economic event's unique global identifier.type (REQUIRED): the type of the object SHOULD be EconomicEvent. If interoperability with other ActivityPub services is desirable, implementers MAY also use object types from Activity Vocabulary, such as Note.attributedTo (REQUIRED): the actor who published the economic event.content (OPTIONAL): the description of the economic event or communication about the economic event. The type of content SHOULD be text/html.published (RECOMMENDED): the date and time at which the economic event was published.to (REQUIRED): the audience of the economic event.action (REQUIRED): the type of economic transaction. If the economic event is an input, the value of this property SHOULD be one of: work, use, consume, cite, pickup, accept. It the economic event is an output, the value of this property SHOULD be one of: produce, deliverService, dropoff, modify. See the action definitions and behaviors for more information.fulfills: the commitment the economic event is completely or partially fulfilling, REQUIRED if there is sucn a commitment, otherwise N/A.inputOf: the process for which this economic event is an input. Either inputOf or outputOf is REQUIRED.outputOf: the process for which this commitment is an output. Either inputOf or outputOf is REQUIRED.context (OPTIONAL): the plan the economic event is part of.resourceConformsTo (RECOMMENDED): the type of an economic resource (can be a skill or type of work for the work action). Could be any URI.resourceQuantity: the amount and unit of the non-effort-based economic resource. Either resourceQuantity or effortQuantity is RECOMMENDED, and both can be included in case of use. This is an object with two properties:hasUnit (REQUIRED): name of the unit, according to Ontology of units of Measure classification.hasNumericalValue (REQUIRED): amount of the resource.effortQuantity: the amount and unit of the work or use effort-based action. Either resourceQuantity or effortQuantity is RECOMMENDED, and both can be included in case of use. This is an object with two properties:hasUnit (REQUIRED): name of the unit, according to Ontology of units of Measure classification.hasNumericalValue (REQUIRED): amount of the resource.provider (RECOMMENDED): the actor who provided the resource. If not included, it is assumed to be the attributedTo actor.receiver (RECOMMENDED): the actor who received the resource. If not included, it is assumed to be the to actor.finished (OPTIONAL): set to true if this economic event completes the commitment. (Note more than one economic event can be recorded against the same commitment.){\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"EconomicEvent\": \"vf:EconomicEvent\",\n \"inputOf\": \"vf:inputOf\",\n \"receiver\": \"vf:receiver\",\n \"provider\": \"vf:provider\",\n \"action\": \"vf:action\",\n \"resourceConformsTo\": \"vf:resourceConformsTo\",\n \"effortQuantity\": \"vf:effortQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\",\n \"finished\": \"vf:finished\"\n }\n ],\n \"type\": \"Create\",\n \"id\": \"https://project.example/activities/ad2f7ee1-6567-413e-a10b-72650cbdc932/create\",\n \"actor\": \"https://project.example/actors/bob\",\n \"object\": {\n \"type\": \"EconomicEvent\",\n \"id\": \"https://project.example/inputs/ad2f7ee1-6567-413e-a10b-72650cbdc932\",\n \"attributedTo\": \"https://project.example/actors/bob\",\n \"context\": \"https://project.example/plans/ddde9d6f-6f3b-4770-a966-3a18ef006931\",\n \"published\": \"2024-10-21T14:16:41.843794Z\",\n \"fulfills\": \"https://project.example/inputs/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider4\",\n \"inputOf\": \"https://project.example/processes/ddde9d6f-6f3b-4770-a966-3a18ef006931#cider\",\n \"action\": \"work\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q3485549\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"6.5\"\n },\n \"provider\": \"https://project.example/actors/bob\",\n \"receiver\": \"https://project.example/actors/gleaning-collective\",\n \"finished\": true\n },\n \"to\": \"https://project.example/actors/alice\"\n}\n"},{"location":"fep/3264/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/37f2/","title":"Index","text":"hide: - navigation description: \"A FEP proposing that W3C Social Web Incubator Community Group harmonize its process with other W3C Groups as well as the Fediverse Enhancement Process on socialhub.activitypub.rocks by * posting Calls for Consensus on the SWICG mailing list public-swicg@w3.org * engaging other SWICG fora like socialhub.activitypub.rocks (linked to as \"Forum\" from the SWICG Webpage) * having a shared response period\"
"},{"location":"fep/37f2/#fep-37f2-a-policy-for-calls-for-consensus-on-swicg-group-decisions","title":"FEP-37f2: a policy for calls for consensus on SWICG group decisions","text":"Authors Status Date received Tracking issue Discussions Repository bengo https://mastodon.social/@bengoDRAFT 2023-09-28 #184 - codeberg"},{"location":"fep/37f2/#summary","title":"Summary","text":"A FEP proposing that W3C Social Web Incubator Community Group harmonize its process with other W3C Groups as well as the Fediverse Enhancement Process on socialhub.activitypub.rocks by: * posting Calls for Consensus on the SWICG mailing list public-swicg@w3.org * engaging other SWICG fora like socialhub.activitypub.rocks (linked to as \"Forum\" from the SWICG Webpage) * having a shared response period
"},{"location":"fep/37f2/#introduction","title":"Introduction","text":"The Social Web Incubation Community Group is missing an explicit decision-making policy, which essentially all other W3C community groups have to ensure asynchronous and healthy consensus mechanisms across timezones and participatory modes.
"},{"location":"fep/37f2/#proposal","title":"Proposal","text":"W3C SWICG will seek to make decisions through consensus and due process, per the W3C Process Document, \u00a75.2.1 Consensus.
To afford asynchronous decisions and organizational deliberation, any resolution (including publication decisions) taken in a face-to-face meeting or teleconference will be considered provisional.
A call for consensus (CFC) will be issued for all resolutions via email to public-swicg@w3.org (archives). The presence of formal resolutions will be indicated by a \"CFC\" prefix in the subject line of the email. Additional outreach to community venues for more affirmative consent is strongly encouraged. There will be a response period of 14 days. If no sustained objections are raised by the end of the response period, the resolution will be considered to have consensus as a resolution of the Community Group, i.e. a group decision.
All decisions made by the group should be considered resolved unless and until new information becomes available or unless reopened at the discretion of the Chairs or the Director.
This policy is an operational agreement per the W3C Community and Business Group Process.
"},{"location":"fep/37f2/#context","title":"Context","text":""},{"location":"fep/37f2/#w3c-groups-with-similar-decision-policies","title":"W3C Groups with Similar Decision Policies","text":"These community groups and working groups have similar decision policies with tentative meeting resolutions and confirmation of calls for consensus via email:
Proposal processes on SWICG Forum with identical response period:
W3C SWICG is a W3C Community Group (CG).
CGs are described in their process document as follows (excerpted for concision):
This document defines W3C Community Groups, where anyone may develop Specifications, hold discussions, develop tests, and so on, with no participation fee. \u2026
Community Groups that develop specifications do so under policies designed to strike a balance between ease of participation and safety for implementers and patent holders \u2026
A Community Group may adopt operational agreements\u2026 that establish the group\u2019s scope of work, decision-making processes, communications preferences, and other operations. \u2026
The following rules govern Community Group operational agreements: * They must be publicly documented. * They must be fair and must not unreasonably favor or discriminate against any group participant or their employer. * They must not conflict with or modify this Community and Business Group Process, the Community Contributor License Agreement (CLA), or the Final Specification Agreement. \u2026
the Chair determines the means by which the group adopts and modifies operational agreements. The Chair must give actual notice to the participants of any material changes to the agreements. Participants may resign from the group if they do not wish to participate under the new agreements. \u2026
Note: W3C encourages groups adopt decision-making policies that promote consensus. \u2026
Each Community Group must have at least one Chair who is responsible for ensuring the group fulfills the requirements of this document as well as the group\u2019s operational agreements.
"},{"location":"fep/37f2/#related-reading","title":"Related Reading","text":"The title of this proposal was generated in line with norms established by Content addressed vocabulary for extensions and FEP-a4ed: The Fediverse Enhancement Proposal Process.
\u26a1 P='a policy for calls for consensus on SWICG group decisions'\n\u26a1 echo \"SWIP-$(echo -n \"$P\" | sha256sum | cut -c-4): $P\"\nSWIP-37f2: a policy for calls for consensus on group decisions\n The 'SW' in 'SWIP' stands for 'Social Web'.
This proposal was initially published at: * https://socialweb.coop/SWIP/37f2/a-policy-for-calls-for-consensus-on-swicg-group-decisions/
"},{"location":"fep/37f2/#copyright","title":"Copyright","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/3b86/","title":"Index","text":"hide: - navigation description: \"\"Activity Intents\" extend the capabilities of an ActivityPub server beyond a user's outbox, and enable direct interactions with content on the wider social web. They do this by publishing a machine-readable list of public URLs where users can perform key activities (such as Follow, Like, or Announce) allowing other websites to initiate remote social interactions without cumbersome copying/pasting of URL strings.\"
"},{"location":"fep/3b86/#fep-3b86-activity-intents","title":"FEP-3b86: Activity Intents","text":"Authors Status Date received Tracking issue Discussions Repository Ben Pate \\<@benpate@mastodon.social>DRAFT 2024-04-19 #302 - codeberg"},{"location":"fep/3b86/#summary","title":"Summary","text":"\"Activity Intents\" extend the capabilities of an ActivityPub server beyond a user's outbox, and enable direct interactions with content on the wider social web. They do this by publishing a machine-readable list of public URLs where users can perform key activities (such as Follow, Like, or Announce) allowing other websites to initiate remote social interactions without cumbersome copying/pasting of URL strings.
The key words \"MUST\", \"SHOULD\", and \"MAY\" are to be interpreted as described in RFC2119.
For the purposes of this document, a \"Home Server\" is the location where a user is logged in, and that publishes the Actor's Activity Intents via WebFinger. A \"Remote Server\" is another location on the Internet that the user wants to interact with, and that will request/receive those intents via WebFinger.
"},{"location":"fep/3b86/#2-history","title":"2. History","text":"Most centralized social media services have widgets that allow users on the wider Internet to interact with their social services. These include \"like\" and \"share\" buttons that third-party websites embed into their content, and link users back to their corresponding social media account.
This is difficult to achieve in a federated environment.
There have been other attempts to make a \"Share on Mastodon\" button that performs a similar action on the Fediverse. But these tasks are implemented differently by different Fediverse servers. For example, Mastodon uses /share, while Hubzilla uses /rpost. Parameters to each application are often different, using variations of text, title, url, and other values.
The lack of a unified standard has led developers to hard-code endpoints for each distinct application. This is brittle and vulnerable to changes by server authors. It also pins those URL endpoints, making them difficult for servers to change in the future without breaking an unknown number of \"share\" buttons out in the wild.
What is needed is a systematic way for each server to announce the endpoint URLs that they support.
"},{"location":"fep/3b86/#3-activity-intents","title":"3. Activity Intents","text":"In the most basic terms, Activity Intents expand on the common Fediverse use of WebFinger in FEP-4adb to include mappings between any Activity Type and the URL endpoint where that user can perform it. This expands and standardizes the \"remote follow\" workflow that was used by oStatus protocol, but has not been fully implemented by newer Fediverse applications and no longer has a public specification document.
When generating a WebFinger result for a user account, servers supporting Activity Intents SHOULD respond with one or more intent links in the \"links\" property. Activity Intent links MUST have rel and template properties. All others properties are ignored.
While the number and specific behavior of each Activity Intent is defined independently by each home server, to maximize compatibility home servers SHOULD publish all of the activities that they support. This gives remote servers the greatest opportunity to match as many activities as possible.
"},{"location":"fep/3b86/#31-example","title":"3.1. Example","text":"Here is an example response from a WebFinger server which includes three Activity Intents appended to the end of its links property.
{\n \"subject\": \"acct:benpate@mastodon.social\",\n \"aliases\": [\n \"https://mastodon.social/@benpate\",\n \"https://mastodon.social/users/benpate\"\n ],\n \"links\": [\n {\n \"rel\": \"http://webfinger.net/rel/profile-page\",\n \"type\": \"text/html\",\n \"href\": \"https://mastodon.social/@benpate\"\n },\n {\n \"rel\": \"self\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://mastodon.social/users/benpate\"\n },\n {\n \"rel\": \"http://ostatus.org/schema/1.0/subscribe\",\n \"template\": \"https://mastodon.social/authorize_interaction?uri={uri}\"\n },\n {\n \"rel\": \"https://w3id.org/fep/3b86/Follow\",\n \"template\": \"https://mastodon.social/authorize_interaction?uri={object}\"\n },\n {\n \"rel\": \"https://w3id.org/fep/3b86/Create\",\n \"template\": \"https://mastodon.social/share?uri={object}\"\n },\n {\n \"rel\": \"https://w3id.org/fep/3b86/Like\",\n \"template\": \"https://mastodon.social/intents/like?id={object}\"\n }\n ]\n}\n"},{"location":"fep/3b86/#32-required-link-properties","title":"3.2. Required Link Properties","text":"rel: Activity Intents use the https://w3id.org/fep/3b86/* (as described in FEP-888d) to designate the kind of activity intent, where * represents the particular Activity the user intends to perform. These relations -- such as https://w3id.org/fep/3b86/Follow, and https://w3id.org/fep/3b86/Create -- are listed in detail below.
template: Links use URI Templates as used in RFC-6415 to designate URLS and parameters to be injected by the caller -- as in {uri} or {name} values. Note: WebFinger does not specify the template parameter, but it is used here because 1) there's a semantic benefit in identifying values not a links, but as link templates, and 2) it is already in use by oStatus, which should make it familiar to Fediverse developers.
Parameter names are chosen to correspond with Activity Vocabulary properties and may differ from parameters used by pre-existing implementations.
To prevent unrecognized properties from corrupting a workflow: * Remote servers MUST be able to replace all recognized values with the appropriate string. * Remote servers MUST replace unrecognized values with an empty string.
"},{"location":"fep/3b86/#33-template-parameters","title":"3.3 Template Parameters","text":"In all cases, Activity Intents intentionally use the property names defined in the W3C standard Activity Vocabulary. However, intents must fit into a URL, and must use commonly understood fields. So, Intent parameters can only use IDs -- URL references to JSON-LD resources available elsewhere on the Internet.
In addition, remote servers MUST Percent Encode all values replaced in the URI Template.
"},{"location":"fep/3b86/#34-workflow-actions","title":"3.4 Workflow Actions","text":"Activity Intents MAY include additional query parameters on-success and on-cancel that allow home servers to return users to their original workflow on the remote server:
on-success - If present in the link template, this value identifies the action that the home server SHOULD take once the Activity Intent workflow is complete. If this parameter is missing, then the resulting page is left up to the home server to choose.
on-cancel - If present in the link template, this value identifies the action that the home server SHOULD take if they abort the Activity Intent workflow. If this parameter is missing, then the resulting page is left up to the home server to choose.
"},{"location":"fep/3b86/#action-close","title":"Action: (close)","text":"If the value of either on-success or on-cancel parameter is the string (close) then the home server can assume that it is running in a pop-up window, and SHOULD close the current window.
If the value of either on-success or on-cancel query parameter is a valid URL, then the home server SHOULD redirect clients to the provided URL. When redirecting to a new URL, the home server MUST use an interstitial page that notified the user that they are being redirected, and displays the URL of the new page to them.
IMPORTANT! Unvalidated Redirects are a documented vulnerability that opens the home server up to phishing attacks.
To avoid a potential security vulnerability when handling on-success and on-cancel workflows, it is important that the home server: \"Force all redirects to first go through a page notifying users that they are going off of your site, with the destination clearly displayed, and have them click a link to confirm.\" (owasp.org)
The user's home server is a trusted environment that manages the user's sign-in status along with the rest of their social inbox and outbox. When the remote server links to an Activity Intent provided by the home server, the layout, fields, and UI are all determined by the home server
Remote servers MAY open Activity Intent links in many different environments, such as: 1. a full browser window 2. a small pop-up window 3. a mobile app HTML view 4. or other constrained environments.
Home servers SHOULD build their UI with minimal assumptions so that the endpoint will fit well into as many environments as possible. Home servers may not be aware if the remote server opens the Activity Intent link in a separate popup window or not, so remote servers SHOULD include on-success and on-cancel parameters that redirect back to its own pages so that it can close the pop-up itself.
This intent corresponds to the ActivityStreams Accept activity and is defined using the link relation https://w3id.org/fep/3b86/Accept.
The Accept intent publishes the API endpoint where the current user can \"accept\" the designated object.
"},{"location":"fep/3b86/#411-parameters","title":"4.1.1. Parameters","text":"{object} - ID of the object that the user will accept when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform they abort the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Accept\",\n \"template\": \"https://server.org/intents/accept?objectId={object}\"\n}\n"},{"location":"fep/3b86/#42-add-intent","title":"4.2. Add Intent","text":"This intent corresponds to the ActivityStreams Add activity and is defined using the link relation https://w3id.org/fep/3b86/Add .
The Add intent publishes the API endpoint where the current user can add an object to the designated collection..
"},{"location":"fep/3b86/#421-parameters","title":"4.2.1. Parameters","text":"{object} ID of the object that the user will add when they use this workflow.{target} ID of the collection being added to.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Add\",\n \"template\": \"https://server.org/intents/add?objectId={object}&targetId={target}\"\n}\n"},{"location":"fep/3b86/#43-announce-intent","title":"4.3. Announce Intent","text":"This intent corresponds to the ActivityStreams Announce activity and is defined using the link relation https://w3id.org/fep/3b86/Announce.
The Announce intent publishes the API endpoint where the current user can announce, or \"boost\" the provided document in their home server.
"},{"location":"fep/3b86/#431-parameters","title":"4.3.1. Parameters","text":"{object} - ID of the document that the user will boost when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Announce\",\n \"template\": \"https://server.org/intents/announce?objectId={object}\"\n}\n"},{"location":"fep/3b86/#44-arrive-intent","title":"4.4. Arrive Intent","text":"This intent corresponds to the ActivityStreams Arrive activity and is defined using the link relation https://w3id.org/fep/3b86/Arrive.
The Arrive intent publishes the API endpoint where the current user can indicate that they have arrived at a particular location from their home server.
"},{"location":"fep/3b86/#441-parameters","title":"4.4.1. Parameters","text":"{location} - ID of the location object where the user will mark as \"arrived\" when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Arrive\",\n \"template\": \"https://server.org/intents/arrive?hotelId={location}\"\n}\n"},{"location":"fep/3b86/#45-block-intent","title":"4.5. Block Intent","text":"This intent corresponds to the ActivityStreams Block activity and is defined using the link relation https://w3id.org/fep/3b86/Block.
The Block intent publishes the API endpoint where the current user can block the provided object from their home server.
"},{"location":"fep/3b86/#451-parameters","title":"4.5.1. Parameters","text":"{object} - ID of the object (document, user, etc) that the user will block when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Block\",\n \"template\": \"https://server.org/intents/block?userId={object}\"\n}\n"},{"location":"fep/3b86/#46-create-intent","title":"4.6. Create Intent","text":"This intent corresponds to the ActivityStreams Create activity and is defined using the link relation https://w3id.org/fep/3b86/Create.
The Create intent publishes the API endpoint where the current user can create a new post in their own outbox.
This is similar to the existing \"share\" endpoints supported by several Fediverse apps, where the user can create a new post in their inbox starting with some pre-populated content.
This Intent differs slightly from others in that it does not take an object ID as a parameter because it expects a the user's home server to create a new object. Instead, the parameters for this Intent are meant to pre-populate into the new object that the user will create.
"},{"location":"fep/3b86/#461-parameters","title":"4.6.1. Parameters","text":"{type} - (optional) Type of object to create (Note, Article, etc). Home servers can determine whether to use or ignore this value.{name} - (optional) Name to pre-populate into the created object.{summary} - (optional) Summary to pre-populate into the created object.{content} - (optional) Text content to pre-populate into the created object.{inReplyTo} - (optional)The ID of the ActivityStreams Document that the user is replying to.{attachment} - (optional) ID of an object (such as a link or an image) that should be included in the created object.{tag} - (optional) ID of an object (such as a tag definition) that should be referenced by the created object{startTime} - (optional) a date-time that identifies the actual or expected starting time of the created object. RFC3339 format {endTime} - (optional) a date-time that identifies the actual or expected ending time of the created object. RFC3339 format{describes} (optional) the ID of an object to be described, when creating a Profile object.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Create\",\n \"template\": \"https://mastodon.social/share?text={content}\"\n}\n"},{"location":"fep/3b86/#47-delete-intent","title":"4.7. Delete Intent","text":"This intent corresponds to the ActivityStreams Delete activity and is defined using the link relation https://w3id.org/fep/3b86/Delete.
The Delete intent publishes the API endpoint where the current user can initiate a \"delete\" request.
"},{"location":"fep/3b86/#471-parameters","title":"4.7.1. Parameters","text":"{object} - ID of the object that the user will delete when they use this workflow.{origin}- (optional) ID of the collection or context that the object will be deleted from.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Delete\",\n \"template\": \"https://server.org/intents/Delete?objectId={object}\"\n}\n"},{"location":"fep/3b86/#48-dislike-intent","title":"4.8. Dislike Intent","text":"This intent corresponds to the ActivityStreams Dislike activity and is defined using the link relation https://w3id.org/fep/3b86/Dislike.
The Dislike intent publishes the API endpoint where the current user can initiate a \"dislike\" request.
"},{"location":"fep/3b86/#481-parameters","title":"4.8.1. Parameters","text":"{object} - ID of the document that the user will dislike when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Dislike\",\n \"template\": \"https://server.org/intent/dislike?objectId={object}\"\n}\n"},{"location":"fep/3b86/#49-flag-intent","title":"4.9. Flag Intent","text":"This intent corresponds to the ActivityStreams Flag activity and is defined using the link relation https://w3id.org/fep/3b86/Flag.
The Flag intent publishes the API endpoint where the current user can initiate a \"flag\" request, which is used to report inappropriate content.
"},{"location":"fep/3b86/#491-parameters","title":"4.9.1. Parameters","text":"{object} - ID of the object (document, user, etc) that the user will flag when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Flag\",\n \"template\": \"https://server.org/intent/flag?objectId={object}\"\n}\n"},{"location":"fep/3b86/#410-follow-intent","title":"4.10. Follow Intent","text":"This intent corresponds to the ActivityStreams Follow activity and is defined using the link relation https://w3id.org/fep/3b86/Follow.
The Follow intent publishes the API endpoint where the current user can initiate a \"follow\" request. This is similar to the remote follow workflow defined by oStatus that is still supported at various levels by several Fediverse apps, but is no longer formally documented.
"},{"location":"fep/3b86/#4101-parameters","title":"4.10.1. Parameters","text":"{object} - ID of the actor that the user will follow when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Follow\",\n \"template\": \"https://mastodon.social/authorize_interaction?uri={object}\"\n}\n"},{"location":"fep/3b86/#411-ignore-intent","title":"4.11. Ignore Intent","text":"This intent corresponds to the ActivityStreams Ignore activity and is defined using the link relation https://w3id.org/fep/3b86/Ignore.
The Ignore intent publishes the API endpoint where the current user can initiate an \"ignore\" request, which is similar to a Block, and is used to ignore or mute various actors and objects online
"},{"location":"fep/3b86/#4111-parameters","title":"4.11.1 Parameters","text":"{object} - ID of the object that the user will mark \"ignored\" when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Ignore\",\n \"template\": \"https://server.org/intents/ignore?objectId={object}\"\n}\n"},{"location":"fep/3b86/#412-invite-intent","title":"4.12. Invite Intent","text":"This intent corresponds to the ActivityStreams Invite activity and is defined using the link relation https://w3id.org/fep/3b86/Invite.
The Invite intent publishes the API endpoint where the current user can initiate an \"invite\" request, which is similar to an Offer, and is used to extend an invitation for the object to the target
"},{"location":"fep/3b86/#4121-parameters","title":"4.12.1. Parameters","text":"{target} - ID of the actor who will receive the invitation.{object} - ID of the object (event, group, etc) that the actor will be invited to.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Invite\",\n \"template\": \"https://server.org/intents/invite?actorId={object}&eventId={target}\"\n}\n"},{"location":"fep/3b86/#413-join-intent","title":"4.13. Join Intent","text":"This intent corresponds to the ActivityStreams Join activity and is defined using the link relation https://w3id.org/fep/3b86/Join.
The Join intent publishes the API endpoint where the current user can initiate a \"join\" request.
"},{"location":"fep/3b86/#4131-parameters","title":"4.13.1. Parameters","text":"{object} - ID of the object that the user will join when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Join\",\n \"template\": \"https://server.org/intents/join?objectId={object}\"\n}\n"},{"location":"fep/3b86/#414-leave-intent","title":"4.14. Leave Intent","text":"This intent corresponds to the ActivityStreams Leave activity and is defined using the link relation https://w3id.org/fep/3b86/Leave.
The Leave intent publishes the API endpoint where the current user can initiate a \"leave\" request.
"},{"location":"fep/3b86/#4141-parameters","title":"4.14.1. Parameters","text":"{object} - ID of the object that the user will leave when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Leave\",\n \"template\": \"https://server.org/intents/leave?objectId={object}\"\n}\n"},{"location":"fep/3b86/#415-like-intent","title":"4.15. Like Intent","text":"This intent corresponds to the ActivityStreams Like activity and is defined using the link relation https://w3id.org/fep/3b86/Like.
The Like intent publishes the API endpoint where the current user can like the current document.
"},{"location":"fep/3b86/#4151-parameters","title":"4.15.1. Parameters","text":"{object} - ID of the object that the user will mark as \"liked\" when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Like\",\n \"template\": \"https://server.com/intents/like?objectId={object}\"\n}\n"},{"location":"fep/3b86/#416-listen-intent","title":"4.16. Listen Intent","text":"This intent corresponds to the ActivityStreams Listen activity and is defined using the link relation https://w3id.org/fep/3b86/Listen.
The Listen intent publishes the API endpoint where the current user can initiate a \"listen\" request.
"},{"location":"fep/3b86/#4161-parameters","title":"4.16.1. Parameters","text":"{object} - ID of the object that the user will mark as \"listened\" to when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Listen\",\n \"template\": \"https://server.org/intents/listen?objectId={object}\"\n}\n"},{"location":"fep/3b86/#417-move-intent","title":"4.17. Move Intent","text":"This intent corresponds to the ActivityStreams Move activity and is defined using the link relation https://w3id.org/fep/3b86/Move.
The Move intent publishes the API endpoint where the current user can initiate a \"move\" request.
"},{"location":"fep/3b86/#4171-parameters","title":"4.17.1. Parameters","text":"{object} - ID of the object that the user will move when they use this workflow.{target} - ID of the collection that the object will be moved to.{origin} - (optional) ID of the collection that the object will be moved from.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Move\",\n \"template\": \"https://server.org/intents/move?objectId={object}&destId={target}\"\n}\n"},{"location":"fep/3b86/#418-offer-intent","title":"4.18. Offer Intent","text":"This intent corresponds to the ActivityStreams Offer activity and is defined using the link relation https://w3id.org/fep/3b86/Offer.
The Offer intent publishes the API endpoint where the current user can initiate an \"offer\" request.
"},{"location":"fep/3b86/#4181-parameters","title":"4.18.1. Parameters","text":"{object} - ID of the object that the user will offer when they use this workflow.{target} - ID of the actor that will receive the offer.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Offer\",\n \"template\": \"https://server.org/intents/offer?objectId={object}&to={target}\"\n}\n"},{"location":"fep/3b86/#419-question-intent","title":"4.19. Question Intent","text":"This intent corresponds to the ActivityStreams Question activity and is defined using the link relation https://w3id.org/fep/3b86/Question.
The Question intent publishes the API endpoint where the current user can initiate an \"question\" workflow.
"},{"location":"fep/3b86/#4191-parameters","title":"4.19.1. Parameters","text":"{name} - The \"name\" property to pre-populate into the question the user will ask when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Question\",\n \"template\": \"https://server.org/intents/question?name={name}\"\n}\n"},{"location":"fep/3b86/#420-read-intent","title":"4.20. Read Intent","text":"This intent corresponds to the ActivityStreams Read activity and is defined using the link relation https://w3id.org/fep/3b86/Read.
The Read intent publishes the API endpoint where the current user can initiate an \"read\" request.
"},{"location":"fep/3b86/#4201-parameters","title":"4.20.1. Parameters","text":"{object} - ID of the object that the user will mark as \"read\" when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Read\",\n \"template\": \"https://server.org/intents/object?objectId={object}\"\n}\n"},{"location":"fep/3b86/#421-reject-intent","title":"4.21. Reject Intent","text":"This intent corresponds to the ActivityStreams Reject activity and is defined using the link relation https://w3id.org/fep/3b86/Reject.
The Reject intent publishes the API endpoint where the current user can initiate an \"reject\" request.
"},{"location":"fep/3b86/#4211-parameters","title":"4.21.1. Parameters","text":"{object} - ID of the object that the user will reject when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Reject\",\n \"template\": \"https://server.org/intents/reject?offerId={object}\"\n}\n"},{"location":"fep/3b86/#422-remove-intent","title":"4.22. Remove Intent","text":"This intent corresponds to the ActivityStreams Remove activity and is defined using the link relation https://w3id.org/fep/3b86/Remove.
The Remove intent publishes the API endpoint where the current user can initiate a \"remove\" request.
"},{"location":"fep/3b86/#4221-parameters","title":"4.22.1. Parameters","text":"{object} - ID of the object that the user will remove when they use this workflow.{target} - (optional) The ID of the collection that the object will be removed from.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Reject\",\n \"template\": \"https://server.org/intents/reject?objectId={object}\"\n}\n"},{"location":"fep/3b86/#423-tentativeaccept-intent","title":"4.23. TentativeAccept Intent","text":"This intent corresponds to the ActivityStreams TentativeAccept activity, which itself is a specialization of the Accept activity and is defined using the link relation https://w3id.org/fep/3b86/TentativeAccept.
The TentativeAccept intent publishes the API endpoint where the current user can initiate an \"tentative accept\" request, indicating that acceptance of the original offer is tentative.
"},{"location":"fep/3b86/#4231-parameters","title":"4.23.1. Parameters","text":"{object} - ID of the object that the user will tentatively accept when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/TentativeAccept\",\n \"template\": \"https://server.org/intents/tentativeAccept?objectId={object}\"\n}\n"},{"location":"fep/3b86/#424-tentativereject-intent","title":"4.24. TentativeReject Intent","text":"This intent corresponds to the ActivityStreams TentativeReject activity, which itself is a specialization of the Reject activity and is defined using the link relation https://w3id.org/fep/3b86/TentativeReject.
The TentativeReject intent publishes the API endpoint where the current user can initiate an \"tentative reject\" request, indicating that rejection of the original offer is tentative.
"},{"location":"fep/3b86/#4241-parameters","title":"4.24.1. Parameters","text":"{object} - ID of the object that the user will tentatively reject when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/TentativeReject\",\n \"template\": \"https://server.org/intents/tentativeReject?objectId={object}\"\n}\n"},{"location":"fep/3b86/#425-travel-intent","title":"4.25. Travel Intent","text":"This intent corresponds to the ActivityStreams Travel activity and is defined using the link relation https://w3id.org/fep/3b86/Travel.
The Travel intent publishes the API endpoint where the user can initiate a \"travel\" request.
"},{"location":"fep/3b86/#4251-parameters","title":"4.25.1. Parameters","text":"{target} - (optional) The ID of the location that the actor will travel to.{origin} - (optional) The ID of the location that the actor will travel from.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Travel\",\n \"template\": \"https://server.org/intents/travel?from={origin}&to={target}\"\n}\n"},{"location":"fep/3b86/#426-undo-intent","title":"4.26. Undo Intent","text":"This intent corresponds to the ActivityStreams Undo activity and is defined using the link relation https://w3id.org/fep/3b86/Undo.
The Undo intent publishes the API endpoint where the current user can initiate a \"travel\" request.
"},{"location":"fep/3b86/#4261-parameters","title":"4.26.1. Parameters","text":"{object} - ID of the activity that the actor will undo.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) URL to redirect users if they abort the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Undo\",\n \"template\": \"https://server.org/intents/undo?activityId={object}\"\n}\n"},{"location":"fep/3b86/#427-update-intent","title":"4.27. Update Intent","text":"This intent corresponds to the ActivityStreams Update activity and is defined using the link relation https://w3id.org/fep/3b86/Update.
The Update intent publishes the API endpoint where the user can initiate an \"update\" request.
"},{"location":"fep/3b86/#4271-parameters","title":"4.27.1. Parameters","text":"{object} - ID of the object that the actor will update when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Update\",\n \"template\": \"https://server.org/intents/update?objectId={object}\"\n}\n"},{"location":"fep/3b86/#428-view-intent","title":"4.28. View Intent","text":"This intent corresponds to the ActivityStreams View activity and is defined using the link relation https://w3id.org/fep/3b86/View.
The View intent publishes the API endpoint where the current user can initiate an \"update\" request.
"},{"location":"fep/3b86/#4281-parameters","title":"4.28.1. Parameters","text":"{object} - ID of the object that the actor will mark as \"viewed\" when they use this workflow.{on-success} - (optional) Workflow action to perform after the workflow completes.{on-cancel} - (optional) Workflow action to perform if the user aborts the workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/View\",\n \"template\": \"https://server.org/intents/view?objectId={object}\"\n}\n"},{"location":"fep/3b86/#50-additional-intent-definitions","title":"5.0. Additional Intent Definitions","text":"In addition to the activities defined in the Activity Vocabulary spec, this FEP defines a few additional intents that home severs SHOULD publish, and remote servers MAY implement.
"},{"location":"fep/3b86/#51-object-intent","title":"5.1 Object Intent","text":"This intent corresponds to the ActivityStreams Object definition, and is intended as a way for users to open an object from a remote server directly in their home server. This is analogous to copying the URL of the object in question, then pasting the URL into the search box of the user's home server.
This intent only opens the object using the user's home server. It does not trigger an ActivityPub activity. Because of this, it does not use the on-success and on-cancel workflows because there is no action to succeed, and no action to cancel.
{object} - ID of the object that the actor will open in their home server when they use this workflow.{\n \"rel\": \"https://w3id.org/fep/3b86/Object\",\n \"template\": \"https://server.org/intents/object?objectId={object}\"\n}\n"},{"location":"fep/3b86/#60-security-considerations","title":"6.0. Security Considerations","text":""},{"location":"fep/3b86/#61-csrf-issues","title":"6.1 CSRF issues","text":"It is important to reiterate some key security practices to prevent Cross Site Request Forgery vulnerabilities.
GET requests to Home Servers.Activity Intents includes the ability for home servers to redirect browsers to new URLs based on the on-success and on-cancel query parameters. If improperly implemented, this has the potential to expose an Unvalidated Redirect vulnerability, which can open the home server up to phishing attack.
To avoid a potential security vulnerability when handling on-success and on-cancel workflows, it is important that the home server: \"Force all redirects to first go through a page notifying users that they are going off of your site, with the destination clearly displayed, and have them click a link to confirm.\" (owasp.org)
There is another good description of this issue OAuth 2.0 Security Best Current Practice \u00a7 4.11. Open Redirection
"},{"location":"fep/3b86/#70-remote-servers-the-rest-of-the-equation","title":"7.0. Remote Servers: The Rest of the Equation","text":"This FEP provides the prerequisite information required for a \"Home Server\" publish Activity Intents for its Actors. It does not specify how \"Remote Servers\" will use this information - i.e., how they implement \"share\" and \"like\" buttons in their content.
"},{"location":"fep/3b86/#71-remote-server-example","title":"7.1. Remote Server Example","text":"Here is a brief example of the workflow as implemented by a remote server:
localStorage)on-success parameter to redirect the user back to the correct page on the remote server.on-cancel parameter to redirect the user back to the correct page on the remote server.Remote servers MAY also account for applications that do not publish Activity Intents, but whose endpoints are still well known. In this case, remote servers SHOULD use Activity Intents links if they are present, then fall back to older links (such as the oStatus /authorize_interaction endpoint) if they are present, then fall back to hard-coded values (such as the Mastodon /share endpoint) if no other values exist.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/400e/","title":"FEP-400e: Publicly-appendable ActivityPub collections","text":"Authors Status Date received Date final Tracking issue Discussions Repository Gregory Klyushnikov activitypub@grishka.meFINAL 2021-02-16 2022-02-04 #5 - codeberg"},{"location":"fep/400e/#summary","title":"Summary","text":"In social media, it's a frequent pattern when there's a collection owned by someone that other people can contribute to. Examples include:
Currently, there is no generic way to signify that an object was created as part of a collection and should only be considered in its context.
This proposal describes how ActivityPub servers and clients could specify collections to which objects created by their actors belong.
"},{"location":"fep/400e/#requirements","title":"Requirements","text":"The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].
"},{"location":"fep/400e/#publicly-appendable-collections","title":"Publicly-appendable collections","text":"A publicly-appendable collection is any collection where it is expected that someone other than its owner could add items but over which its owner retains complete authority. For example, a wall is a collection to which other people could add posts (Notes), but from which its owner could delete any posts as well as restrict who can add them.
A publicly-appendable collection SHOULD have a valid and globally-unique id that SHOULD point to either a Collection or an OrderedCollection object that contains the links to all its objects.
If an actor has publicly-appendable collections, its server MAY include them as additional fields in its ActivityPub representation. For example, user actors may specify the link to their walls, or groups may specify the link to the collection of their photo albums.
Implementations MAY use the presence or absence of specific collection to determine whether the actor's server supports features that depend on that collection and alter their UIs accordingly.
"},{"location":"fep/400e/#using-target-in-objects","title":"Usingtarget in objects","text":"If an ActivityPub object is being created as part of a collection, the object SHOULD include the target field that contains an abbreviated collection object, which SHOULD contain at least the following fields:
type \u2014 either Collection or OrderedCollection.id \u2014 the id of the collection.attributedTo \u2014 the id of the owner of the collection. This is necessary to simplify the database design on the receiving side.While [Activity Vocabulary] specifies target as a field with similar semantics in activities, it's important to include it in objects themselves so any software that only sees the object without its enclosing Create activity, e.g. when following a link form another object or retrieving the object from a user-provided URI, unambiguously knows that it should only be considered in the context of its collection.
When an ActivityPub server receives in its inbox a correctly signed Create activity with an object that has the target field, it does the following:
attributedTo or id fields of the abbreviated collection object.attributedTo doesn't match the actual owner of the collection specified by id, or if the collection owner is not a local actor, the server SHOULD abort processing and MAY return 400 Bad Request.403 Unauthorized or respond with 200 OK and later send a Reject{Create} activity to the originating server.id in its local storage as belonging to the specified collection.Add activity to any parties that might be concerned with it. The target field in the activity SHOULD only be the collection id, and the object field SHOULD be the id of the object being added. It is RECOMMENDED that this activity is sent to all the collection owner's followers for the sake of data consistency, and it SHOULD be sent to the actor that created the object being added.Add activity","text":"When an ActivityPub server receives in its inbox a correctly signed Add activity, it SHOULD do the following:
actor either from local storage or from the network.object.target is the ID of a collection owned by actor. If it is not, abort processing and return 400 Bad Request.target in the activity matches target.id in the object and that target.attributedTo in the object matches the actor ID. If it does not, abort processing and return 400 Bad Request.id in its local storage as belonging to the specified collection.Add activity","text":"{\n \"@context\":\"https://www.w3.org/ns/activitystreams\",\n \"actor\":\"https://example.com/users/1\",\n \"id\":\"https://example.com/posts/41864/activityAdd\",\n \"to\":[\n \"https://www.w3.org/ns/activitystreams#Public\",\n \"https://example.com/users/1/followers\",\n \"https://example.com/users/6946\"\n ],\n \"type\":\"Add\",\n \"object\":\"https://example.com/posts/41864\",\n \"target\":\"https://example.com/users/1/wall\"\n}\n"},{"location":"fep/400e/#deleting-an-object-from-a-collection","title":"Deleting an object from a collection","text":"Since the collection owner has complete authority over the contents of the collection, they can delete any objects from it. When an object is deleted from a collection by its owner, their server SHOULD send a Delete activity to at least the server of the actor that created the object; it's also RECOMMENDED that this activity is sent to all the servers that Add was sent to. Those servers then SHOULD delete the object as if the deletion was initiated by its creator.
In some use cases, it might make sense to allow objects to be moved between collections, for example, a group moderator might want to move a photo between photo albums in a group, or a forum moderator might want to split some messages into a separate thread. It's only possible to move objects between collections that are owned by the same actor.
When moving an object between collections, the collection owner SHOULD send a Move activity to at least the server of the object creator, specifying the target collection and the id of the object; it's also RECOMMENDED that this activity is sent to all the servers that Add was sent to. Those servers then SHOULD update the target field in their stored copies of the object.
The requirement of an Add activity sent by a collection owner largely prevents a bad actor from effectively adding something to a collection against the collection owner's will while also helping data consistency across servers. However, there's still one case when this is possible. When a server fetches an object that has a target field directly, for example when a user has entered its URL into a search box or when it is referenced by a field such as inReplyTo, there's presently no reliable way to verify whether the object actually belongs to the collection.
This proposal is implemented in Smithereen for both user and group walls since the following commit: https://github.com/grishka/Smithereen/commit/de013593dde06a3091ecfbd32960a694d79c146e
"},{"location":"fep/400e/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/4adb/","title":"FEP-4adb: Dereferencing identifiers with webfinger","text":"Authors Status Date received Tracking issue Discussions Repository Helge helge.krueger@gmail.comDRAFT 2023-05-13 #100 - codeberg"},{"location":"fep/4adb/#summary","title":"Summary","text":"In this FEP, we will formalize the process of dereferencing an URI using webfinger in order for usage in ActivityPub. The main goal is to enable the usage of URIs of the form acct:user@domain or did:example:12345 as ids for objects used in ActivityPub. While this FEP only discusses this in the context of actors, it should be applicable for general objects. In order for a smooth introduction, it is recommended to start deployment with actor objects.
This FEP first presents the algorithm and examples, then discusses the usage in the context of the Fediverse. This means the first two sections are for people wanting to implement this FEP, the following sections are for people wanting to decide if this FEP is a good idea.
"},{"location":"fep/4adb/#the-algorithm","title":"The algorithm","text":"[Webfinger] allows us to associate an ActivityStreams object with a pair given by an URI and a domain. This is done through the following steps:
GET https://domain/.well-known/webfinger?resource={URI}.\"type\":\"application/activity+json\".We will denote this object by ActivityStreams(URI, domain). There are now two cases to resolve an URI:
domain(URI)In case 1, it is clear that we associate ActivityStreams(URI, domain(URI)) to the URI. In case 2, we will use the domain associated with the @id of the document the document the URI appeared in. If the document was received through a POST request and doesn't contain an id, or the id is an URI, the domain the POST request originated from should be used. In this case a verification that the URI can be associated with the object MUST be performed. This can for example be achieved through FEP-c390.
We start with the acct URI Scheme. In order to deliver the activity
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Follow\",\n \"actor\": \"https://chatty.example/ben/\",\n \"object\": \"acct:alyssa@social.example\",\n \"to\": \"acct:alyssa@social.example\",\n}\n the ActivityPub server should perform the lookup for ActivityStreams(\"acct:alyssa@social.example\", \"social.example\"), i.e. the request
GET https://social.example/.well-known/webfinger?resource=acct:alyssa@social.example\n We now turn our attention to DIDs. In order to resolve the author of
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://chatty.example/ben/456\",\n \"type\": \"Note\",\n \"attributedTo\": \"did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5\",\n \"to\": \"acct:alyssa@social.example\",\n \"content\": \"Hello\"\n}\n one has to resolve the URI did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5. As this URI does not contain a domain, the domain from the id, i.e. chatty.example is used, so the request
GET https://chatty.example/.well-known/webfinger?resource=did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5\n is made. The resulting ActivityStreams object should contain
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://chatty.example/bnm789\",\n \"attachment\": {\n \"type\": \"VerifiableIdentityStatement\",\n \"subject\": \"did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5\",\n \"alsoKnownAs\": \"https://chatty.social/bnm789\",\n \"proof\": { ... }\n },\n ...\n}\n following FEP-c390 so that the authority of ben to use the did can be verified.
"},{"location":"fep/4adb/#background","title":"Background","text":"Let's start with Section 3.1 of ActivityPub. It starts with the sentence
All Objects in [ActivityStreams] should have unique global identifiers.
We first note that the word unique does not appear in [ActivityStreams]. Second it is unclear why it is there and what it means:
We will assume that it is the second interpretation. In particular, this means that we can associated multiple identifiers with the same ActivityStreams object.
Next comes the following line in [ActivityPub]
Publicly dereferencable URIs, such as HTTPS URIs, with their authority belonging to that of their originating server. (Publicly facing content SHOULD use HTTPS URIs).
The essential point of this FEP is to extend the range of publicly dereferencable URIs to contain basically any URI by using [Webfinger].
The current usage of Webfinger in the Fediverse is asymmetric. As discussed in MastoGuide, one can associate many URIs of the form acct:user@domain.tld with the same Actor, by just making webfinger return an appropriate response. However, only one acct-URI can be associated with an Actor. This is done by:
acct:{preferredUsername}@{domain of actor id}\n where preferredUsername is from the actor object.
We now discuss applications of the approach of this FEP to the actor object.
"},{"location":"fep/4adb/#preferred-account","title":"Preferred Account","text":"We will follow this suggestion in [alsoKnownAs and acct:]. The main idea is to associate multiple accounts. For this, we will return to alyssa@social.example, whose Actor object would look like the following omitting irrelevant details
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://social.example/abc123\",\n \"preferredUsername\": \"alyssa\",\n ...\n}\n By the algorithm discussed in background, we can associate the URI acct:alyssa@social.example with this. Suppose now that Alyssa owns the domain alyssa.cool, and set up WebFinger to answer to
GET https://alyssa.cool/.well-known/webfinger?resource=me@alyssa.cool\n with a link to the above actor. Alyssa would then be disappointed because Fediverse software is not displaying her cooler username. To remedy this, we propose the addition to the actor object
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"xrd\": \"http://docs.oasis-open.org/ns/xri/xrd-1.0#\",\n \"aliases\": {\n \"@id\": \"xrd:Alias\",\n \"@type\": \"@id\",\n \"@container\": \"@list\"\n },\n }\n ],\n \"id\": \"https://social.example/abc123\",\n \"preferredUsername\": \"alyssa\",\n \"aliases\": [\"acct:me@alyssa.cool\", \"acct:alyssa@social.example\"],\n ...\n}\n a new generation of Fediverse software will then be able to display the account of Alyssa as me@alyssa.cool.
Continuing with Alyssa from above, by relying on FEP-612d, she may even associate @alyssa.cool with her account. Unfortunately, a proper URI format is still missing.
Let's return to Ben and him wanting to use a decentralized identifier, see did-core and did-method-key
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"xrd\": \"http://docs.oasis-open.org/ns/xri/xrd-1.0#\",\n \"aliases\": {\n \"@id\": \"xrd:Alias\",\n \"@type\": \"@id\",\n \"@container\": \"@list\"\n },\n }\n ],\n \"id\": \"https://chatty.social/bnm789\",\n \"preferredUsername\": \"ben\",\n \"aliases\": [\"did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5\", \"acct:ben@chatty.social\"],\n ...\n}\n The problem here is that, we do not know if the the did-key belongs to BEN. To remedy this, we will use FEP-c390. This means that we need to add
\"attachment\": {\n \"type\": \"VerifiableIdentityStatement\",\n \"subject\": \"did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5\",\n \"alsoKnownAs\": \"https://chatty.social/bnm789\",\n \"proof\": { ... }\n }\n with an appropriate proof to Ben's actor object.
"},{"location":"fep/4adb/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/4ccd/","title":"FEP-4ccd: Pending Followers Collection and Pending Following Collection","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@prodromou.nameDRAFT 2023-06-21 #129 Discussions codeberg"},{"location":"fep/4ccd/#summary","title":"Summary","text":"This ActivityPub extension defines two collections, pendingFollowers and pendingFollowing, with which users can review and manage their pending follow requests.
ActivityPub represents a directed social graph with followers and following properties on actors. To initiate a relationship, a Follow activity is sent from the potential follower to the followed actor, who can Accept or Reject it.
Between the time that a Follow is sent and the time that it is accepted or rejected, the relationship is in a pending state. This is not represented in the ActivityPub data model.
The new pendingFollowers collection can be used to review incoming Follow activities to Accept or Reject them.
The new pendingFollowing collection can be used to review outgoing Follow activities to Undo them.
Because the full activity data is needed to Accept, Reject or Undo, these collections should include Follow activities, and not just the actors requesting to follow.
Note that this extension is primarily useful for clients and servers that implement the ActivityPub API. Furthermore, it is primarily useful for actors that manually approve followers. The manuallyApprovesFollowers property in the ActivityPub Miscellaneous Terms can be used to indicate that an actor manually approves followers.
"},{"location":"fep/4ccd/#context","title":"Context","text":"The context document for this ActivityPub extension is at https://purl.archive.org/socialweb/pending. Its contents are as follows:
{\n \"@context\": {\n \"pdg\": \"https://purl.archive.org/socialweb/pending#\",\n \"pendingFollowers\": {\n \"@id\": \"pdg:pendingFollowers\",\n \"@type\": \"@id\"\n },\n \"pendingFollowing\": {\n \"@id\": \"pdg:pendingFollowing\",\n \"@type\": \"@id\"\n },\n \"pendingFollowersOf\": {\n \"@id\": \"pdg:pendingFollowersOf\",\n \"@type\": \"@id\"\n },\n \"pendingFollowingOf\": {\n \"@id\": \"pdg:pendingFollowingOf\",\n \"@type\": \"@id\"\n }\n }\n}\n"},{"location":"fep/4ccd/#semantic-versioning","title":"Semantic versioning","text":"For compatibility, the context document for this extension has aliases using semantic versioning.
https://purl.archive.org/socialweb/pending/1.1.0. This version is immutable.https://purl.archive.org/socialweb/pending/1.1. This version may be updated with bug fixes, documentation, or minor changes, but no new terms.https://purl.archive.org/socialweb/pending/1. This version may be updated with bug fixes or minor changes, and may include new terms, but will not include breaking changes.https://purl.archive.org/socialweb/pending. This version may be updated with bug fixes or minor changes, may include new terms, and may include breaking changes. It is the latest version of the context document.All terms in the context document use the same namespace, https://purl.archive.org/socialweb/pending#.
As with other ActivityPub properties, the values of these properties can be included by a reference URL, or by an embedded node object.
"},{"location":"fep/4ccd/#pendingfollowers","title":"pendingFollowers","text":"URI https://purl.archive.org/socialweb/pending#pendingFollowers Notes pendingFollowers is a collection of Follow activities that have been sent to the actor, but have not yet been accepted or rejected. Items in the collection MUST be in reverse chronological order. Items in the collections MUST be Follow activities. They MUST be unique by id. Each actor of a Follow activity in the collection MUST be unique by id. Domain Object (ActivityPub actor) Range OrderedCollection or Collection Functional Yes"},{"location":"fep/4ccd/#pendingfollowing","title":"pendingFollowing","text":"URI https://purl.archive.org/socialweb/pending#pendingFollowing Notes pendingFollowing is a property of an actor. It is a collection of Follow activities that have been sent by the actor, but have not yet been accepted or rejected. They MUST be unique by id. Each object of a Follow activity in the collection MUST be unique by id. Domain Object (ActivityPub actor) Range OrderedCollection or Collection Functional Yes"},{"location":"fep/4ccd/#pendingfollowersof","title":"pendingFollowersOf","text":"URI https://purl.archive.org/socialweb/pending#pendingFollowersOf Notes This property identifies the actor for which the specified collection is the pendingFollowers collection. It is an inverse property of pendingFollowers. Domain Collection or OrderedCollection Range Object (ActivityPub actor) Functional Yes"},{"location":"fep/4ccd/#pendingfollowingof","title":"pendingFollowingOf","text":"URI https://purl.archive.org/socialweb/pending#pendingFollowingOf Notes This property identifies the actor for which the specified collection is the pendingFollowing collection. It is an inverse property of pendingFollowing. Domain Collection or OrderedCollection Range Object (ActivityPub actor) Functional Yes"},{"location":"fep/4ccd/#examples","title":"Examples","text":"A publisher can include the pendingFollowers and pendingFollowing collection in the properties of an actor.
pendingFollowers and pendingFollowing","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/pending/1\"\n ],\n \"id\": \"https://example.com/evanp\",\n \"type\": \"Person\",\n \"name\": \"Evan Prodromou\",\n \"inbox\": \"https://example.com/evanp/inbox\",\n \"outbox\": \"https://example.com/evanp/outbox\",\n \"following\": \"https://example.com/evanp/following\",\n \"followers\": \"https://example.com/evanp/followers\",\n \"liked\": \"https://example.com/evanp/liked\",\n \"pendingFollowers\": \"https://example.com/evanp/pendingFollowers\",\n \"pendingFollowing\": {\n \"id\": \"https://example.com/evanp/pendingFollowing\",\n \"type\": \"Collection\",\n \"name\": \"Pending following for Evan Prodromou\",\n \"totalItems\": 2\n },\n \"manuallyApprovesFollowers\": true,\n \"to\": [\"as:Public\"]\n}\n Notice that the pendingFollowers property is a URL, while the pendingFollowing property is an embedded object with useful additional properties.
pendingFollowers collection","text":"Retrieving the pendingFollowers collection shows incoming follow requests for this actor.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/pending/1\"\n ],\n \"id\": \"https://example.com/evanp/pendingFollowers\",\n \"type\": \"OrderedCollection\",\n \"attributedTo\": \"https://example.com/evanp\",\n \"pendingFollowersOf\": \"https://example.com/evanp\",\n \"name\": \"Pending followers for Evan Prodromou\",\n \"orderedItems\": [\n {\n \"type\": \"Follow\",\n \"id\": \"https://example.net/alyssa/follow/7\",\n \"summary\": \"Alyssa wants to follow Evan\",\n \"content\": \"Hey, Evan! It's Alyssa from the conference.\",\n \"actor\": {\n \"id\": \"https://example.net/alyssa\",\n \"type\": \"Person\",\n \"name\": \"Alyssa P. Hacker\"\n },\n \"to\": \"https://example.com/evanp\",\n \"cc\": \"as:Public\",\n \"published\": \"2023-06-21T12:00:00Z\"\n },\n {\n \"type\": [\"http://custom.example/ns/Archive\", \"Follow\"],\n \"id\": \"https://social.example/jokebot3000/follow/287\",\n \"summary\": \"Jokebot 3000 wants to follow Evan to archive his jokes\",\n \"actor\": {\n \"id\": \"https://social.example/jokebot3000\",\n \"type\": \"Application\",\n \"name\": \"Jokebot 3000\"\n },\n \"to\": \"https://example.com/evanp\",\n \"cc\": \"as:Public\",\n \"published\": \"2023-05-07T12:00:00Z\"\n }\n ]\n}\n Note that the second, earlier Follow activity has a custom type property. Note also that the object of the Follow activities, which will be the same for every activity, is elided for clarity and space.
pendingFollowing collection","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/pending/1\",\n {\"sports\": \"https://sports.example/ns#\"}\n ],\n \"id\": \"https://example.com/evanp/pendingFollowing\",\n \"type\": \"Collection\",\n \"attributedTo\": \"https://example.com/evanp\",\n \"pendingFollowingOf\": \"https://example.com/evanp\",\n \"name\": \"Pending following for Evan Prodromou\",\n \"items\": [\n {\n \"type\": [\"sports:Fan\", \"Follow\"],\n \"id\": \"https://example.com/evanp/fan/309\",\n \"summary\": \"Evan is a fan of Jimena\",\n \"actor\": \"https://example.com/evanp\",\n \"object\": {\n \"id\": \"https://tennis.example/jimena\",\n \"type\": \"Person\",\n \"name\": \"Jimena Suarez\"\n },\n \"to\": \"https://tennis.example/jimena\",\n \"cc\": \"as:Public\",\n \"published\": \"2023-04-19T12:00:00Z\"\n },\n {\n \"type\": \"Follow\",\n \"id\": \"https://example.net/evanp/follow/214\",\n \"summary\": \"Evan wants to follow Montreal Weather Updates\",\n \"actor\": \"https://example.com/evanp\",\n \"object\": {\n \"id\": \"https://weather.example/canada/quebec/montreal\",\n \"type\": \"Service\",\n \"name\": \"Montreal Weather Updates\"\n },\n \"to\": \"https://weather.example/canada/quebec/montreal\",\n \"cc\": \"as:Public\",\n \"published\": \"2023-02-11T12:00:00Z\"\n }\n ]\n}\n Note that the first Follow activity has a custom type property. Also note that even though the collection's type is Collection and the items property is items, the activities still must be in reverse chronological order.
An actor that manually approves followers SHOULD include the manuallyApprovesFollowers property in their actor object, with a value of true. This indicates that the actor will review and approve or reject incoming follow requests.
When a server receives an otherwise valid Follow activity from a client, it SHOULD add that activity to the pendingFollowing collection of the sending actor. The server SHOULD also add the Follow activity to the pendingFollowers collection of the actor that is being followed, if the followed actor is on the same server.
When a server receives an otherwise valid Follow activity from another server, it SHOULD add that activity to the pendingFollowers collection of the followed actor.
When a server receives an Accept or Reject activity with a Follow activity as object from a client, it SHOULD remove that Follow activity from the pendingFollowers collection of the actor. The server SHOULD also remove the Follow activity from the pendingFollowing collection of the actor that initiated the follow, if the following actor is on the same server.
When a server receives an Accept or Reject activity with a Follow activity as object from another server, it SHOULD remove that Follow activity from the pendingFollowing collection of the receiving actor.
When a server receives an Undo activity with a Follow activity as object from a client, it SHOULD remove that Follow activity from the pendingFollowing collection of the actor that sent the Follow. It SHOULD also remove the Follow activity from the pendingFollowers collection of the actor that was followed, if the followed actor is on the same server.
When a server receives an Undo activity with a Follow activity as object from another server, it SHOULD remove that Follow activity from the pendingFollowers collection of the receiving actor.
The following Harel statechart illustrates the state transitions for a Follow activity between actors A1 and A2. The states show which objects belong to which collections, and the transitions show when activities are received and processed via the client API and then the server federation protocol.
The pendingFollowers and pendingFollowing collections are sensitive information about an actor's social connections. For privacy, some services and actors do not share the following or followers collections. If not similarly protected, the pendingFollowers and pendingFollowing collections could be used to infer information about the actor's social connections before they are established.
Some services or actors do not forward Reject activities to the actor of a Follow activity. Harassing or abusive actors may try to determine if the actor has rejected their follow request by fetching the pendingFollowers collection.
For these reasons, publishers SHOULD NOT make the pendingFollowers and pendingFollowing collections visible to unauthenticated users. Publishers SHOULD NOT make the pendingFollowers and pendingFollowing collections visible to authenticated users who are not the actor.
pendingFollowers and pendingFollowing collections.pendingFollowers and pendingFollowing collections.CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/4f05/","title":"Index","text":"hide: - navigation description: \"The standard CRUD (Create, Read, Update, Delete) behaviours in ActivityPub specify a single Delete activity for use in all cases. This is insufficient to describe two-stage deletion, often referred to as \"soft\" and \"hard\" deletion. Not all software implements two-stage deletion, and so the behaviours described here progressively enhance the functionality for those supporting it, while retaining backward compatibility otherwise.\"
"},{"location":"fep/4f05/#fep-4f05-soft-deletion","title":"FEP-4f05: Soft Deletion","text":"Authors Status Date received Tracking issue Discussions Repository Julian Lam julian@nodebb.org, Angus McLeod angus@pavilion.techDRAFT 2025-04-15 #554 Discussions codeberg"},{"location":"fep/4f05/#summary","title":"Summary","text":"The standard CRUD (Create, Read, Update, Delete) behaviours in ActivityPub specify a single Delete activity for use in all cases. This is insufficient to describe two-stage deletion, often referred to as \"soft\" and \"hard\" deletion.
Not all software implements two-stage deletion, and so the behaviours described here progressively enhance the functionality for those supporting it, while retaining backward compatibility otherwise.
"},{"location":"fep/4f05/#assumptions","title":"Assumptions","text":"A blog post by kaniini advocates for the treatment of copies of remote data as a cached representation. From there we derive the following assumptions:
Delete activity should be treated as a request to refresh the locally cached copy or delete it otherwise.The Forums and Threaded Discussions Task Force (ForumWG) has identified a common nomenclature when referring to organized objects in a threaded discussion model.
When an object is soft deleted, the object's ActivityPub representation MUST be updated to Tombstone. Servers SHOULD continue to respond to requests for the object with a 200-level response code; the object continues to exist in-place.
A Delete activity SHOULD be published in order to propagate the soft deletion to other servers.
When an object is hard deleted, the object MUST no longer have an ActivityPub representation. Servers MUST respond to requests for the object with a 400-level response code. A 404 Not Found is acceptable, although a 410 Gone sends a more explicit signal that the object was explicitly removed. Security or privacy considerations may affect your decision to send anything more than a 404.
A Delete activity MUST be published in order to propagate the hard deletion to other servers.
When a Delete activity is encountered, the referenced object MAY be either the full object or a reference to one.
Verifying authenticity of the embedded object is out of scope of this FEP. Follow the origin-based security model for verifying authenticity of any received Delete activity.
If object is a reference, the server MUST request the object (via its id) from the origin server directly.
Handle the received activity as below based on the received response code or object type.
Note
The actor may not match the attributedTo of the targeted object. This is allowed as moderators or privileged users may carry out deletions.
Tombstone","text":"The local object SHOULD be soft deleted as per the local implementor's standard behaviour.
"},{"location":"fep/4f05/#not-a-tombstone","title":"Not aTombstone","text":"Update the object's local representation if applicable.
"},{"location":"fep/4f05/#http-404-or-410","title":"HTTP 404 or 410","text":"The local object SHOULD be hard deleted as per the local implementor's standard behaviour.
"},{"location":"fep/4f05/#unexpected-responses","title":"Unexpected responses","text":"The section above (\"Receivers\") details how a received Delete activity is handled. If during the course of the backreference check the object type or response code differs from expectations, the retrieved state supercedes the activity.
e.g. A Delete is received, but a backreference check returns a 200 with a Note-type object. Therefore it is \"Not a Tombstone\" even though the activity received stated otherwise.
The inverse also applies.
e.g. An Undo(Delete) is received, but a backreference check returns a Tombstone. There fore it is still treated as a Tombstone even though the activity received stated otherwise.
An earlier implementation of two-stage object deletion published an Update(Tombstone), but this approach was deemed superfluous as it signified the same effect as a Delete\u2014to proceed with a cache invalidation and update.
It is safe to assume the majority of ActivityPub-enabled software does not support two-stage object deletion. Publishing a Delete ensures that the intended behaviour of a soft deletion\u2014that the object's content is no longer visible\u2014is carried through to other servers.
Implementors are free to handle a soft deletion in the way they prefer (e.g. NodeBB will continue to associate the post (object) with the original actor, and simply blank out the content for non-privileged users.) This FEP explicitly does not specify how individual implementors should handle local representations of remote data.
"},{"location":"fep/4f05/#who-to-address","title":"Who to address","text":"The recipients list of the published Delete activity is outside the scope of this document.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/521a/","title":"FEP-521a: Representing actor's public keys","text":"Authors Status Type Date received Date final Tracking issue Discussions Repository silverpill @silverpill@mitra.socialFINAL implementation 2023-07-08 2025-06-14 #130 Discussions codeberg"},{"location":"fep/521a/#summary","title":"Summary","text":"This proposal describes how to represent public keys associated with ActivityPub actors.
"},{"location":"fep/521a/#rationale","title":"Rationale","text":"Historically, Fediverse services used publicKey property to represent actor's public key. Implementations usually allow only one key per actor, therefore a new approach is needed to support use cases where additional keys are required.
Furthermore, publicKey property is considered deprecated and is not present in the latest version of Security Vocabulary.
The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/521a/#multikey","title":"Multikey","text":"Each public key MUST be represented as an object with Multikey type, as defined in section 2.2.2 Multikey of Controlled Identifiers specification. This object MUST have the following properties:
id: the unique global identifier of the public key.type: the value of this property MUST be the string Multikey.controller: the value of this property MUST match actor ID.publicKeyMultibase: a Multibase encoded value of a Multicodec prefix and the public key. Implementations MUST use the base-58-btc alphabet.Key identifiers MUST be absolute URIs.
Identifiers SHOULD be generated by appending a fragment identifier to the actor ID. That allows consumers to retrieve both actor object and associated keys with a single HTTP request. If actor's keys are identified using fragment identifiers, each key MUST have a unique fragment identifier.
Resolution of a URI containing a fragment identifier is performed using the algorithm specified in section 3.4 Fragment Resolution of Controlled Identifiers specification.
"},{"location":"fep/521a/#key-types","title":"Key types","text":"Implementers can use cryptographic keys of any type for which a Multicodec prefix is registered.
"},{"location":"fep/521a/#adding-keys-to-an-actor-object","title":"Adding keys to an actor object","text":"Multikey objects are added to the actor object, which is considered a controlled identifier document, as described in Controlled Identifiers specification.
If the key is intended to be used for signing ActivityPub objects, it MUST be added to the assertionMethod array in the actor object.
Other use cases are out of scope of this proposal.
Implementations are discouraged from adding any objects to the assertionMethod array that do not conform to this specification. Implementations encountering non-conformant entries in the assertionMethod array SHOULD ignore them.
An actor MAY have no associated public keys.
"},{"location":"fep/521a/#example","title":"Example","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/cid/v1\"\n ],\n \"type\": \"Person\",\n \"id\": \"https://server.example/users/alice\",\n \"inbox\": \"https://server.example/users/alice/inbox\",\n \"outbox\": \"https://server.example/users/alice/outbox\",\n \"assertionMethod\": [\n {\n \"id\": \"https://server.example/users/alice#ed25519-key\",\n \"type\": \"Multikey\",\n \"controller\": \"https://server.example/users/alice\",\n \"publicKeyMultibase\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n }\n ]\n}\n"},{"location":"fep/521a/#security-considerations","title":"Security considerations","text":"Each actor is expected to have a different secret key.
To prevent side-channel attacks, secret keys need to be stored in binary form. If stored as text, they need to be decoded using a constant-time algorithm.
Warning
Controlled Identifiers specification requires implementers to use the base-58-btc alphabet for multibase-encoded secret keys. This practice is discouraged because available implementations of Base58 decode algorithm may not be constant-time.
See fep-521a.feature
"},{"location":"fep/521a/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/521a/fep-521a-test-case/","title":"Actor Objects must express signing key as assertionMethod Multikey","text":""},{"location":"fep/521a/fep-521a-test-case/#background","title":"Background","text":"FEP-521a defines how Actor object MUST express the public key for its signing key in the assertionMethod property, as a Multikey object of a given shape and id URI shape.
This is a Test Case testing conformance with the one behavior specified in FEP-521a.
"},{"location":"fep/521a/fep-521a-test-case/#test-subject","title":"Test Subject","text":"The subject of this test is any data claiming to conform to the specification of an ActivityPub Actor Object and to the FEP-521a extension thereof.
This test is not directly applicable to an ActivityPub Server. An ActivityPub Server serves 0 or more Actor Objects. An ActivityPub Server for a big community might serve hundreds of ActivityPub Actor Objects. An ActivityPub Server for a single human may serve only that person's ActivityPub Actor Object.
This test applies to Actor Objects, but not all Actor Objects are addressable by an HTTPS URL. The URI that addresses an Actor Object is not the same as the Actor Object. A given URL may resolve to different Actor Objects in different contexts, and a given Actor Object may not be universally addressable across context by any one URL.
"},{"location":"fep/521a/fep-521a-test-case/#inputs","title":"Inputs","text":"This test requires the following inputs:
actor - the actor object under testedinapplicable.inapplicable.assertionMethod arrayinapplicable.This test applies directly to the actor input.
actor is not an Actor Object, the outcome MUST be inapplicable.actor is not a JSON object, the outcome MUST be inapplicable.actor JSON does not have a type property, the outcome MUST be inapplicable.actor JSON does not have an assertionMethod property, the outcome MUST be inapplicable.assertionMethod array is a distinct test target, referred to below as assertionMethod[x]Multikey is inapplicable (warning)Multikey passes or fails the tests of its validity as a MultikeyassertionMethod[x].id - MUST be a stringassertionMethod[x].controller - MUST match the id property, if present, of the assertionMethod array's parent (i.e., the Actor object)assertionMethod[x].publicKeyMultibase - MUST be a base58btc-encoded and appropriately-prefixed (in this case, beginning with z) expression of a binary public key expressionFor the purposes of determining whether the input actor is expressing a key in the shape defined by the FEP:
assertionMethod property, containing an array of 0 or more objectsid property, containing a stringtype property, containing a stringcontroller property, containing a stringpublicKeyMultibase property, containing a stringFor the purposes of determining whether the input actor is expressing 1 or more keys all the following must be true:
assertionMethod array, once parsed as JSON, ...id property, which is a URIassertionMethod.id property, which is a string unique within the actor objectassertionMethod.type property whose value is the string \"Multikey\" or an Array containing the string \"Multikey\"assertionMethod.controller property whose value is a string identical to the parent object's id property valueassertionMethod.publicKeyMultibase property equal to a string of base58btc-alphabet characters, i.e. matching the regular expression [1-9A-HJ-NP-Za-km-z]+These are test cases for this test case, and can be used to verify that an implementation of this test case specification will be consistent with other implementations.
"},{"location":"fep/521a/fep-521a-test-case/#missing-assertionmethod","title":"Missing assertionMethod","text":"input
actor:
{\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\"\n}\n test result
inapplicableinput
actor:
{\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"assertionMethod\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n}\n test targets
inapplicable, warning (malformed assertionMethod array)input
actor:
{\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"assertionMethod\": [{\n \"inappropriateKey\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n }]\n}\n test targets
inapplicable, warning (malformed assertionMethod member)input
actor:
{\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"assertionMethod\": {[\n \"id\": \"https://example.com/#ed25519-key\",\n \"type\": \"Multikey\",\n \"controller\": \"https://example.com/\",\n \"publicKeyMultibase\": \"6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n ]}\n}\n test targets
inapplicable, warning (malformed assertionMethod member)input
actor:
{\n\"type\": \"Person\",\n\"inbox\": \"https://example.com/inbox\",\n\"outbox\": \"https://example.com/outbox\",\n\"id\": \"https://example.com/\",\n\"assertionMethod\": [\n {\n \"id\": \"https://example.com/#ed25519-key\",\n \"type\": \"Multikey\",\n \"controller\": \"https://https://example.com/\",\n \"publicKeyMultibase\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n },\n {\n \"inappropriateKey\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n }\n]\n}\n test targets
assertionMethod[0]passedassertionMethod[1]inapplicable, warning (non-conformant entries)outcome","text":"An outcome is a conclusion that comes from evaluating a test on a test subject. An outcome can be one of the three following types:
inapplicable: No part of the test subject matches the applicabilitypassed: A test target meets all expectationsfailed: A test target does not meet all expectationsassertionMethod has outcome passed, requirement is satisfiedassertionMethod has outcome failed, requirement is not satisfiedassertionMethod has outcome inapplicable, further testing is needed to determine whether this requirement is satisfiedWITHDRAWN 2022-08-23 2025-06-24 #18 Discussions codeberg"},{"location":"fep/5624/#summary","title":"Summary","text":"Sometimes, users may want to share an information or a story without inviting replies from outside their circles or from anyone at all. In particular, individuals may want to restrict who can reply to them in order to avoid \u201creply guys\u201d or limit outright harassment, while instutions may want to disable replies on their posts to provide information without having to deal with a moderation burden.
This can be broken into an advisory part advertising what sets of actors are expected to be able to reply, and a collaborative verification process where third-parties check with the actor being replied to that the reply is indeed allowed.
"},{"location":"fep/5624/#requirements","title":"Requirements","text":"The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].
In the remaining of this FEP, \u201cdistribution authority\u201d (or \u201cauthority\u201d for short) refers to an actor that controls the distribution and audience of replies. The purpose of this wording is to make this FEP applicable both for models where replies are first-class posts, and for \u201cpost and comments\u201d models where comments only exist in the context of a post and the post author decides who gets to see the comments. In the absence of extensions, the \u201cauthority\u201d is the author of the post being replied to.
"},{"location":"fep/5624/#declaring-a-reply-policy","title":"Declaring a reply policy","text":"In order to advertise who is allowed to reply to an object, an author MAY set the canReply (http://joinmastodon.org/ns#canReply) property on their objects. If set, this property MUST be an empty array or one or more actors or collections.
To ease implementation, collections SHOULD be restricted to one of the following: - as:Public, to indicate that anyone can reply - the authority's followers collection (if defined) - the authority's followed collection (if defined)
In addition, canReply SHOULD contain every actor mentioned in the original object.
Whenever one of these collections is used, the receiving end can easily know whether they are expected to be able to reply.
"},{"location":"fep/5624/#example-object","title":"Example object","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\": \"http://joinmastodon.org/ns#\",\n \"canReply\": \"toot:canReply\"\n }\n ],\n \"attributedTo\": \"https://example.com/users/1\",\n \"id\": \"https://example.com/users/1/statuses/1\",\n \"type\": \"Note\",\n \"content\": \"Hello world\",\n \"canReply\": \"https://www.w3.org/ns/activitystreams#Public\"\n}\n"},{"location":"fep/5624/#checking-whether-the-user-can-reply-and-submitting-the-reply-to-the-authority","title":"Checking whether the user can reply and submitting the reply to the authority","text":"When an object with canReply is set, it SHOULD be conveyed in human-readable form to the user if possible, for instance with something like \u201cOnly mentioned users can reply\u201d or \u201cOnly people Authority follows and mentioned users can reply\u201d.
The software SHOULD NOT offer the user to reply unless it is directly mentioned in the object's tag attribute or listed in canReply (either directly or through a collection), or canReply contains a collection for which the recipient cannot efficiently check the membership of the would-be replier.
After locally verifying that the replier should be allowed to reply, the replier's end SHOULD POST the Create activity for the reply to the authority's inbox only, and consider the reply to be pending approval.
When receiving a reply to an object with a canReply property, the authority decides whether the reply is acceptable.
If the reply is considered acceptable, the authority MUST reply with an ApproveReply (http://joinmastodon.org/ns#ApproveReply) activity with the object property set to the id of the reply object, and its inReplyTo property set to the object it is in reply to.
That ApproveReply activity SHOULD be publicly dereferenceable and MUST be dereferenceable by all parties allowed to see the original post. It MUST NOT embed its object nor its inReplyTo as to avoid possible information leaks.
Additionally, the authority MAY forward an accepted reply according to its own rules.
If the reply is considered unacceptable, the authority SHOULD reply with a RejectReply (http://joinmastodon.org/ns#RejectReply) activity. This activity MAY be publicly accessible, but this is not a requirement.
The reason for using the new activity types ApproveReply and RejectReply is to be explicit about the purpose of the approval, as one could imagine other kinds of approvals, and remaining implicit may cause ambiguities with other potential extensions.
ApproveReply activity","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\": \"http://joinmastodon.org/ns#\",\n \"ApproveReply\": \"toot:ApproveReply\"\n }\n ],\n \"actor\": \"https://example.com/users/1\",\n \"id\": \"https://example.com/reply_approvals/1\",\n \"type\": \"ApproveReply\",\n \"object\": \"https://example.org/users/bob/statuses/3\",\n \"inReplyTo\": \"https://example.com/users/1/statuses/1\"\n}\n"},{"location":"fep/5624/#example-rejectreply-activity","title":"Example RejectReply activity","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\": \"http://joinmastodon.org/ns#\",\n \"RejectReply\": \"toot:RejectReply\"\n }\n ],\n \"actor\": \"https://example.com/users/1\",\n \"id\": \"https://example.com/reply_approvals/1\",\n \"type\": \"RejectReply\",\n \"object\": \"https://example.org/users/bob/statuses/3\"\n}\n"},{"location":"fep/5624/#receiving-approval-and-distributing-the-reply","title":"Receiving approval and distributing the reply","text":"After sending the initial Create, the replier SHOULD wait for an ApproveReply activity such as described above.
Once the ApproveReply has been received, the replier SHOULD add a replyApproval (http://joinmastodon.org/ns#replyApproval) property to their reply object pointing to the ApproveReply activity they received, and then MAY send a Create activity with the modified object to its intended audience.
If it instead receives a RejectReply, the reply SHOULD be immediately deleted and the replier MAY be notified.
replyApproval","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\": \"http://joinmastodon.org/ns#\",\n \"canReply\": \"toot:canReply\",\n \"replyApproval\": \"toot:replyApproval\"\n }\n ],\n \"attributedTo\": \"https://example.com/users/1\",\n \"id\": \"https://example.org/users/bob/statuses/3\",\n \"type\": \"Note\",\n \"content\": \"@alice hello!\",\n \"inReplyTo\": \"https://example.com/users/1/statuses/1\",\n \"canReply\": \"https://www.w3.org/ns/activitystreams#Public\",\n \"replyApproval\": \"https://example.com/reply_approvals/1\",\n \"tag\": {\n \"type\": \"Mention\",\n \"href\": \"https://example.com/users/1\"\n }\n}\n"},{"location":"fep/5624/#verifying-third-party-replies","title":"Verifying third-party replies","text":"When processing a reply from a remote actor to a remote authority, a recipient SHOULD discard any reply that does not match any of the following conditions: - the object it is in reply to does not set a canReply property - the object has been received through the remote authority - the object it is in reply to has a canReply containing the as:Public collection - the author of the reply appears in a Mention object in the tag property of the object it is in reply to - the object it is in reply to sets a non-empty canReply, and replyApproval can be dereferenced and is a valid ApproveReply activity
To be considered valid, the ApproveReply activity referenced in replyApproval MUST satisfy the following properties: - its actor property is the authority - its authenticity can be asserted - its object property is the reply under consideration - its inReplyTo property matches that of the reply under consideration
In addition, if the reply is considered valid, but has no valid replyApproval despite the object it is in reply to having a canReply property, the recipient MAY hide the reply from certain views.
The authority may want to perform /a posteriori/ moderation of their replies.
To do this, the authority SHOULD send a RejectReply activity to the sender and the reply's audience, with the reply URI as the object property. The object property MUST NOT be embedded, as to avoid possible information leaks.
The URI at which the previously-offered ApproveReply was available should return HTTP 404 or redirect to the newly-issued RejectReply activity.
Upon receiving a RejectReply activity for a previously-accepted reply, third-parties SHOULD check that the RejectReply is valid and SHOULD delete or hide the revoked reply if it is.
To be considered valid, the RejectReply activity MUST satisfy the following properties: - its actor property is the authority - its authenticity can be asserted - its object property is the reply under consideration
Because it is unrealistic to expect all implementations and deployments to implement this proposal at the same time, deployment SHOULD be gradual, with verification of third-party replies only performed once the other steps are widely implemented. To encourage adoption without breaking compatibility altogether, implementations MAY want to hide non-validated replies from certain views (e.g. requiring a click to see \u201chidden replies\u201d, or not showing the reply to non-followers).
"},{"location":"fep/5624/#security-considerations","title":"Security considerations","text":"By not adding a hash or copy of the reply in the ApproveReply activity, malicious actors could exploit this in a split horizon setting, sending different versions of the same activity to different actors. This is, however, already a concern in pretty much all contexts in ActivityPub, and enshrining that information in the ApproveReply activity would have many drawbacks:
ApproveReply activity is publicly dereferenceableNone so far.
"},{"location":"fep/5624/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/5711/","title":"FEP-5711: Inverse Properties for Collections","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.orgDRAFT 2025-03-24 #539 Discussions codeberg"},{"location":"fep/5711/#summary","title":"Summary","text":"This FEP defines inverse properties for collections that are important in ActivityPub.
"},{"location":"fep/5711/#motivation","title":"Motivation","text":"The Activity Vocabulary defines a number of important collection properties for organizing reactions to an object. ActivityPub augments these with additional properties that specify the activity streams and social graph for actors.
Out of context, though, it can be hard to determine if a collection has a defined relationship to an object or actor. This can be important for clients that give users the ability to manipulate collections with the Add, Remove, Update and Delete activities, since the special collections defined in ActivityPub are usually maintained with different flows.
Other methods exist to identify collections that are the value of properties of actors. It's possible to use the attributedTo property to identify the actor, and then compare against all of the collection properties of the actor (inbox, outbox, and so on). This is a good fallback mechanism, but the inverse properties defined in this document are a more direct way to determine the relationship. There is no method for determining if a collection is the property of an object, however.
Additionally, one form of spoofing that a server could use is referring to collections as properties of objects that in fact are related to other objects. For example, a server could include a collection of likes as a property of an object that is not the object that the likes are for. This could be used to inflate the apparent popularity of an object. Inverse properties can help prevent this kind of spoofing by providing a two-way verification mechanism.
These inverse properties are most useful when the collections are represented separately from the object or actor they are related to. For example, this representation can help identify the object that the collection contains the likes of:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/b25kr36ief\",\n \"likesOf\": \"https://example.com/image/7pqs5i7c1e\"\n}\n This representation, on the other hand, is redundant, since the collection is represented as the property of the object:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Image\",\n \"id\": \"https://example.com/image/7pqs5i7c1e\",\n \"likes\": {\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/b25kr36ief\",\n \"likesOf\": \"https://example.com/image/7pqs5i7c1e\"\n }\n}\n"},{"location":"fep/5711/#user-stories","title":"User stories","text":"The following terms are defined in this vocabulary extension.
The examples in the following sections refer to the following example object:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Image\",\n \"id\": \"https://example.com/image/7pqs5i7c1e\",\n \"name\": \"A beautiful image\",\n \"url\": \"https://files.example.com/image/29m2p4kdq15.jpg\",\n \"likes\": \"https://example.com/collection/b25kr36ief\",\n \"shares\": \"https://example.com/collection/hvjgqhpv3e\",\n \"replies\": \"https://example.com/collection/u9hwmfih7r\"\n}\n ...and/or the following actor:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Person\",\n \"id\": \"https://example.com/person/bup9a8eqm\",\n \"name\": \"Alice\",\n \"inbox\": \"https://example.com/collection/tgiqwh6p3b\",\n \"outbox\": \"https://example.com/collection/hvf8crcd6r\",\n \"followers\": \"https://example.com/collection/x48dtnhlt9\",\n \"following\": \"https://example.com/collection/r9w7krxua\",\n \"liked\": \"https://example.com/collection/jzc50wc28l\"\n}\n"},{"location":"fep/5711/#likesof","title":"likesOf","text":"https://w3id.org/fep/5711#likesOfas:Collectionas:ObjectDefines an object for which the collection is the value of the likes property.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/b25kr36ief\",\n \"likesOf\": \"https://example.com/image/7pqs5i7c1e\"\n}\n"},{"location":"fep/5711/#sharesof","title":"sharesOf","text":"https://w3id.org/fep/5711#sharesOfas:Collectionas:ObjectDefines an object for which the collection is the value of the shares property.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/hvjgqhpv3e\",\n \"sharesOf\": \"https://example.com/image/7pqs5i7c1e\"\n}\n"},{"location":"fep/5711/#repliesof","title":"repliesOf","text":"https://w3id.org/fep/5711#repliesOfas:Collectionas:ObjectDefines an object for which the collection is the value of the replies property.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"OrderedCollection\",\n \"id\": \"https://example.com/collection/u9hwmfih7r\",\n \"repliesOf\": \"https://example.com/image/7pqs5i7c1e\"\n}\n"},{"location":"fep/5711/#inboxof","title":"inboxOf","text":"https://w3id.org/fep/5711#inboxOfas:Collectionas:Object (an ActivityPub actor)Defines an actor for which the collection is the value of the inbox property.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/tgiqwh6p3b\",\n \"inboxOf\": \"https://example.com/person/bup9a8eqm\"\n}\n"},{"location":"fep/5711/#outboxof","title":"outboxOf","text":"https://w3id.org/fep/5711#outboxOfas:Collectionas:Object (an ActivityPub actor)Defines an actor for which the collection is the value of the outbox property.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/hvf8crcd6r\",\n \"outboxOf\": \"https://example.com/person/bup9a8eqm\"\n}\n"},{"location":"fep/5711/#followersof","title":"followersOf","text":"https://w3id.org/fep/5711#followersOfas:Collectionas:Object (an ActivityPub actor)Defines an actor for which the collection is the value of the followers property.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/x48dtnhlt9\",\n \"followersOf\": \"https://example.com/person/bup9a8eqm\"\n}\n"},{"location":"fep/5711/#followingof","title":"followingOf","text":"https://w3id.org/fep/5711#followingOfas:Collectionas:Object (an ActivityPub actor)Defines an actor for which the collection is the value of the following property.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/r9w7krxua\",\n \"followingOf\": \"https://example.com/person/bup9a8eqm\"\n}\n"},{"location":"fep/5711/#likedof","title":"likedOf","text":"https://w3id.org/fep/5711#likedOfas:Collectionas:Object (an ActivityPub actor)Defines an actor for which the collection is the value of the liked property.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/5711\"\n ],\n \"type\": \"Collection\",\n \"id\": \"https://example.com/collection/jzc50wc28l\",\n \"likedOf\": \"https://example.com/person/bup9a8eqm\"\n}\n"},{"location":"fep/5711/#implementation-notes","title":"Implementation notes","text":"There should be only one inverse relationship property for a collection. There are conceivable structures where the replies collection of an object is also the liked collection of an actor, but these are rare.
All of these properties are functional, meaning that there is only one value for each collection.
"},{"location":"fep/5711/#security-considerations","title":"Security considerations","text":"Claims made by these properties should be verified by retrieving the referenced object or actor and checking the appropriate property. For example, if a server claims that a collection is the shares property of an object using the sharesOf property, the consumer should retrieve the referenced object and check that the shares property references the same collection.
Use of these inverse properties can help prevent spoofing of collections, but they do not provide verification of the contents of the collection. For example, a collection that uses the likesOf property to claim that it is the likes collection of a video, and which is in turn referenced by the video, may still contain Like activities that are not actually likes of the video.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/5bf0/","title":"FEP-5bf0: Collection sorting and filtering","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Michael Puckett michael@puckett.contactWITHDRAWN 2023-04-10 2025-06-14 #80 - codeberg"},{"location":"fep/5bf0/#summary","title":"Summary","text":"This proposal would allow Collections to have a streams property, as Actors do. The streams would be of the type CollectionView, a proposed vocabulary extension that represents a sorted and/or filtered version of a Collection. ActivityPub clients could then render CollectionViews without having to perform such filtering or sorting operations themselves.
Metadata about how the sorting or filtering has been applied would be applied using new proposed vocabulary extensions that leverage SHACL for describing constraints.
"},{"location":"fep/5bf0/#motivations","title":"Motivations","text":"Some ActivityPub clients rely only on C2S protocols for accessing Collections and displaying the nested items.
Currently, in order to support filtering or sorting, these clients need to retrieve all paginated items, assemble them, manually sort or filter them, and then re-paginate them.
This proposal would allow servers to perform these kinds of operations, either at runtime or ahead of time, to ease the burden on clients.
Other servers should be free to explore the Collections, but they can be easily ignored, along with the new properties.
"},{"location":"fep/5bf0/#implementation","title":"Implementation","text":"A CollectionView extends from OrderedCollection and represents a filtered and/or sorted version of a Collection. Similarly, a CollectionViewPage extends from OrderedCollectionPage.
The method of filtering applied to the CollectionView can be indicated via its \"filter\" property, which maps to one or more SHACL Shapes.
The vocabulary would also provide a SHACL Shape for indicating that a property's value is among the items in a particular ActivityStreams Collection.
The method of sorting applied to the CollectionView can be indicated via its \"sort\" property, which maps to a SortShape. A SortShape extends SHACL's PropertyShape and adds the \"order\" property which can be mapped to \"Ascending\" or \"Descending\".
If there is no \"sort\" property, the order is the same as that of the original Collection.
"},{"location":"fep/5bf0/#examples","title":"Examples","text":"Here, an Actor's Inbox returns all Activities posted by the Actor, and the server also provides filtered versions as streams for client consumption.
The first CollectionView returns only the Like Activities.
The second CollectionView returns only created Articles with replies, demonstrating multiple filters and filtering on nested properties.
The third CollectionView demonstrates how to indicates that a given property's value is in a partiular ActivityStreams Collection. In the example, the CollectionView is returning Activities by Alyssa's Co-workers. (Alyssa has a custom stream of mutual followers who she has labeled as Co-workers.)
{\n \"@context\": {\n \"@vocab\": \"https://www.w3.org/ns/activitystreams#\",\n \"fep\": \"https://w3id.org/fep#\",\n \"CollectionView\": \"fep:CollectionView\",\n \"viewOf\": \"fep:viewOf\",\n \"filter\": \"fep:filter\",\n \"sort\": \"fep:sort\",\n \"inCollection\": \"fep:inCollection\",\n \"SortShape\": \"fep:SortShape\",\n \"order\": \"fep:order\",\n \"sh\": \"http://www.w3.org/ns/shacl#\",\n \"PropertyShape\": \"sh:PropertyShape\",\n \"path\": \"sh:path\",\n \"hasValue\": \"sh:hasValue\",\n \"minCount\": \"sh:minCount\"\n },\n \"id\": \"https://example.social/@alyssa/inbox\",\n \"type\": \"OrderedCollection\",\n \"name\": \"Inbox\",\n \"totalItems\": 1000,\n \"first\": \"https://example.social/@alyssa/inbox/page/1\",\n \"last\": \"https://example.social/@alyssa/inbox/page/2\",\n \"streams\": [\n {\n \"id\": \"https://example.social/@alyssa/inbox/likes\",\n \"type\": \"CollectionView\",\n \"name\": \"Likes\",\n \"filter\": {\n \"type\": \"PropertyShape\",\n \"path\": \"type\",\n \"hasValue\": \"Like\"\n },\n \"sort\": {\n \"type\": \"SortShape\",\n \"path\": \"published\",\n \"order\": \"Descending\"\n },\n \"totalItems\": 10,\n \"first\": \"https://example.social/@alyssa/inbox/likes/page/1\",\n \"last\": \"https://example.social/@alyssa/inbox/likes/page/1\"\n },\n {\n \"id\": \"https://example.social/@alyssa/inbox/posts-with-replies\",\n \"type\": \"CollectionView\",\n \"name\": \"Posts with Replies\",\n \"filter\": [\n {\n \"type\": \"PropertyShape\",\n \"path\": \"type\",\n \"hasValue\": \"Create\"\n },\n {\n \"type\": \"PropertyShape\",\n \"path\": [\"object\", \"inReplyTo\"],\n \"minCount\": 1\n }\n ],\n \"sort\": {\n \"type\": \"SortShape\",\n \"path\": \"published\",\n \"order\": \"Descending\"\n },\n \"totalItems\": 10,\n \"first\": \"https://example.social/@alyssa/inbox/blog-posts/page/1\",\n \"last\": \"https://example.social/@alyssa/inbox/blog-posts/page/1\",\n \"viewOf\": \"https://example.social/@alyssa/inbox\"\n },\n {\n \"id\": \"https://example.social/@alyssa/inbox/notes-by-coworkers\",\n \"type\": \"CollectionView\",\n \"name\": \"Posts by Co-Workers\",\n \"filter\": {\n \"type\": \"InCollectionShape\",\n \"path\": \"actor\",\n \"inCollection\": \"https://example.social/@alyssa/friends/coworkers\"\n },\n \"sort\": {\n \"type\": \"SortShape\",\n \"path\": \"published\",\n \"order\": \"Descending\"\n },\n \"totalItems\": 10,\n \"first\": \"https://example.social/@alyssa/inbox/notes-by-coworkers/page/1\",\n \"last\": \"https://example.social/@alyssa/inbox/notes-by-coworkers/page/1\",\n \"viewOf\": \"https://example.social/@alyssa/inbox\"\n }\n ]\n}\n"},{"location":"fep/5bf0/#vocabulary-extensions","title":"Vocabulary Extensions","text":"Here are the terms that would needed to be added to the FEP vocabulary:
{\n \"@context\": {\n \"fep\": \"https://w3id.org/fep#\",\n \"as\": \"https://www.w3.org/ns/activitystreams#\",\n \"sh\": \"http://www.w3.org/ns/shacl#\",\n \"rdf\": \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\",\n \"rdfs\": \"http://www.w3.org/2000/01/rdf-schema#\"\n },\n \"fep:CollectionView\": {\n \"@id\": \"fep:CollectionView\",\n \"@type\": \"rdfs:Class\",\n \"rdfs:subClassOf\": \"as:OrderedCollection\",\n \"rdfs:label\": \"Collection View\",\n \"rdfs:comment\": \"Represents a sorted and/or filtered version of a Collection\"\n },\n \"fep:CollectionViewPage\": {\n \"@id\": \"fep:CollectionViewPage\",\n \"@type\": \"rdfs:Class\",\n \"rdfs:subClassOf\": \"as:OrderedCollectionPage\",\n \"rdfs:label\": \"Collection View Page\",\n \"rdfs:comment\": \"Represents a sorted and/or filtered version of a CollectionPage\"\n },\n \"fep:SortShape\": {\n \"@id\": \"fep:SortShape\",\n \"@type\": \"rdfs:Class\",\n \"rdfs:subClassOf\": \"sh:PropertyShape\",\n \"rdfs:label\": \"Sort Shape\",\n \"rdfs:comment\": \"A PropertyShape with an 'order' property\"\n },\n \"fep:filter\": {\n \"@id\": \"fep:filter\",\n \"@type\": \"rdf:Property\",\n \"rdfs:domain\": \"fep:CollectionView\",\n \"rdfs:range\": \"sh:Shape\",\n \"rdfs:label\": \"Filter\",\n \"rdfs:comment\": \"The method of filtering applied to the CollectionView\"\n },\n \"fep:sort\": {\n \"@id\": \"fep:sort\",\n \"@type\": \"rdf:Property\",\n \"rdfs:domain\": \"fep:CollectionView\",\n \"rdfs:range\": \"fep:SortShape\",\n \"rdfs:label\": \"Sort\",\n \"rdfs:comment\": \"The method of sorting applied to the CollectionView\"\n },\n \"fep:order\": {\n \"@id\": \"fep:order\",\n \"@type\": \"rdf:Property\",\n \"rdfs:domain\": \"fep:SortShape\",\n \"rdfs:range\": \"fep:SortOrderType\",\n \"rdfs:label\": \"Order\",\n \"rdfs:comment\": \"Indicates whether the sort order is 'Ascending' or 'Descending'\"\n },\n \"fep:SortOrderType\": {\n \"@id\": \"fep:SortOrderType\",\n \"@type\": \"rdfs:Class\",\n \"rdfs:label\": \"Sort Order Type\",\n \"rdfs:comment\": \"For indicating the sort order\"\n },\n \"fep:Ascending\": {\n \"@id\": \"fep:Ascending\",\n \"@type\": \"fep:SortOrderType\",\n \"rdfs:label\": \"Ascending\",\n \"rdfs:comment\": \"Indicates ascending sort order\"\n },\n \"fep:Descending\": {\n \"@id\": \"fep:Descending\",\n \"@type\": \"fep:SortOrderType\",\n \"rdfs:label\": \"Descending\",\n \"rdfs:comment\": \"Indicates descending sort order\"\n },\n \"fep:InCollectionShape\": {\n \"@id\": \"fep:InCollectionShape\",\n \"@type\": \"rdfs:Class\",\n \"rdfs:subClassOf\": \"sh:PropertyShape\",\n \"rdfs:label\": \"In Collection Shape\",\n \"rdfs:comment\": \"For filtering on whether a property's value is in a Collection.\",\n \"sh:js\": \"fep:inCollectionFunction\"\n },\n \"fep:inCollection\": {\n \"@id\": \"fep:inCollection\",\n \"@type\": \"rdf:Property\",\n \"rdfs:domain\": \"fep:InCollectionShape\",\n \"rdfs:range\": \"sh:IRI\",\n \"rdfs:label\": \"In Collection\",\n \"rdfs:comment\": \"Maps a Collection URL to fep:inCollectionFunction\"\n },\n \"fep:inCollectionFunction\": {\n \"@id\": \"fep:inCollectionFunction\",\n \"@type\": \"sh:JSFunction\",\n \"sh:jsFunction\": \"inCollection\",\n \"sh:jsLibrary\": \"https://w3id.org/fep/functions.js\",\n \"sh:parameter\": {\n \"@type\": \"sh:Parameter\",\n \"sh:path\": \"fep:inCollection\"\n }\n },\n \"fep:viewOf\": {\n \"@id\": \"fep:viewOf\",\n \"@type\": \"rdf:Property\",\n \"rdfs:label\": \"View of\",\n \"rdfs:domain\": \"fep:CollectionView\",\n \"rdfs:range\": \"as:Collection\",\n \"rdfs:comment\": \"A reference back to the original Collection\"\n }\n}\n"},{"location":"fep/5bf0/#security","title":"Security","text":"Servers could in theory make available a templated URL endpoint that allows for arbitrary sorting or filtering. This should be discouraged, as it could lead to database injections. Instead, only predetermined sorted/filtered CollectionViews should be made available via the streams property.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/5e53/","title":"FEP-5e53: Opt-out Preference Signals","text":"Authors Status Date received Tracking issue Discussions Repository Don Marti dmarti@zgp.orgDRAFT 2024-06-09 #327 Discussions codeberg"},{"location":"fep/5e53/#summary","title":"Summary","text":"Some users have concerns about how their content and/or personal information are used. For example, some users do not want the content they created to be used for training generative AI systems, and some users do not want to have their personal information shared or sold.
Several opt-out preference signals (OOPSs) have been standardized or proposed in the form of HTTP headers that can apply to a connection between a user and a central server. In some jurisdictions, companies that administer web sites are required to process and act on OOPSs.
This FEP extends ActivityPub to support passing OOPSs along with the content and user information to which they may apply. This FEP refers to existing OOPSs that have already been documented, and does not propose new ones.
"},{"location":"fep/5e53/#declaring-an-oops","title":"Declaring an OOPS","text":"In order to apply an OOPS to an object, an author MAY set the JSON-LD properties xRobotsTag and/or SPC. For example,
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://fep.example/ns/privacyHeaders\"],\n \"type\": \"Note\",\n \"content\": \"Don't surveil me bro\",\n \"xRobotsTag\": \"noai\",\n \"SPC\": 1\n}\n A recipient SHOULD process the content and metadata of the object as if the recipient had received it over an HTTP connection with the corresponding header.
SPC: process as if the object has been received in an HTTP connection with the Sec-GPC HTTP request header.
xRobotsTag: process as if the object had been received with an X-Robots-Tag HTTP response header with a value equal to this property.
The existing Global Privacy Control (GPC) standard allows for individuals in an increasing number of jurisdictions to pass a legally binding opt-out preference signal indicating a commonly held privacy norm. However, GPC is implemented as an HTTP request header, which makes it only works from client to server. This limits the ability of users to pass a privacy opt-out in a federated communications medium. A federated system where a user's information may be passed from one server to another is currently at a disadvantage in serving users who wish to opt out, because the opt-out preference signal does not travel with the information to which it applies. SPC is intended to have the same effects as GPC, but for cases where the user does not have a direct HTTP connection to the recipient of some personal information. Adding the SPC opt-out to ActivityPub would bring parity with centralized social sites.
Fediverse instances should make SPC configurable by users, and reflect a user's actual intent to opt out. Fediverse instances should detect when a user has turned on GPC or some other privacy opt-out or setting, and offer to apply SPC to that user's objects.
Content sharing platforms that operate in a centralized manner are now frequently promoting their support for signaling opt-outs to generative AI training, using the noai value for the X-Robots-Tag header. See RobotsMeta and RobotExclusion. In order for federated content sharing systems to provide an attractive alternative to centralized ones, federated systems will likely need to also offer an opt out signaling feature.
"},{"location":"fep/5e53/#security-considerations","title":"Security considerations","text":"This proposal depends on compliant processing of OOPSs by all actors.
Some jurisdictions require privacy OOPSs to be registered in order to have legal effect. In the event this FEP is accepted, the author intends to pursue registration.
"},{"location":"fep/5e53/#implementations","title":"Implementations","text":"None so far.
"},{"location":"fep/5e53/#related-proposal","title":"Related proposal","text":"FEP-c118 suggests establishing a content licensing framework. This may be possible by adapting this FEP to include the license header from WebLinking.
"},{"location":"fep/5e53/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/5feb/","title":"FEP-5feb: Search indexing consent for actors","text":"Authors Status Date received Tracking issue Discussions Repository Claire claire.fep-1d7d@sitedethib.comDRAFT 2023-09-06 #154 - codeberg"},{"location":"fep/5feb/#summary","title":"Summary","text":"This FEP introduces an actor-level attribute that can be used to explicitly express an actor's consent (or lack thereof) to their public objects being indexed for search purposes.
Akin to robots.txt and noindex meta tags, this attribute is advisory and relies on the indexers respecting the directive, as public objects can not technically be prevented from being indexed.
The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].
"},{"location":"fep/5feb/#specifying-search-indexing-consent-at-the-actor-level","title":"Specifying search indexing consent at the actor-level","text":"Actors can use the indexable (http://joinmastodon.org/ns#indexable) attribute to specify whether they consent to their public objects being indexed for search or not.
A missing indexable attribute SHOULD be handled as indexable: false.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\":\"http://joinmastodon.org/ns#\",\n \"indexable\":\"toot:indexable\",\n }\n ],\n \"id\": \"https://example.com/users/1\",\n \"type\":\"Person\",\n \"inbox\": \"https://example.com/users/1/inbox\",\n \"outbox\": \"https://example.com/users/1/outbox\",\n \"preferredUsername\": \"bob\",\n \"indexable\": true\n}\n"},{"location":"fep/5feb/#searchable-objects","title":"Searchable objects","text":"Objects authored by an actor with indexable: true and addressed to the as:Public special collection SHOULD be made available for search.
Objects authored by an actor with indexable: false MUST NOT be made available for search to other users unless they were previously displayed and interacted with (e.g. it is allowed for a user to search posts they have bookmarked).
indexable attribute","text":"Whenever an actor is updated and its attribute is set to indexable: true, its objects SHOULD be made available for search as described in the previous section.
Whenever an actor is updated and its attribute is set to indexable: false, its objects MUST be removed from search as described in the previous section.
Considering this attribute is purely advisory, special care SHOULD be given to the user interface to make sure users are not made to believe the attribute will ensure they never get indexed.
"},{"location":"fep/5feb/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/612d/","title":"FEP-612d: Identifying ActivityPub Objects through DNS","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Helge helge.krueger@gmail.comWITHDRAWN 2023-04-18 2025-10-10 #89 - codeberg"},{"location":"fep/612d/#summary","title":"Summary","text":"In [ActivityPub], objects are identified through their id, which is a dereferenciable URI. For this, one adds a TXT record to DNS with name _apobjid and value corresponding to the URI of the ActivityPub object. If a domain name is then passed to a FediVerse application, it can then perform the DNS lookup, and resolve it to the ActivityPub object.
This FEP is inspired by [BlueSky], and it gets the FediVerse half of the way there. It allows one to look up the ActivityPub Actor from an associated domain name, it does not address the issue of diplaying the domain name instead of the FediVerse handle. One first step would be to include the domain name in alsoKnownAs. Then use some property of the actor to specify, which object to display.
"},{"location":"fep/612d/#other-mechanisms","title":"Other Mechanisms","text":"Two other mechanisms to identify ActivityPub objects beside their id are currently in use:
"},{"location":"fep/612d/#webfinger","title":"Webfinger","text":"The FediVerse handle username@domain.tld is commonly used as an identifier for users on the FediVerse. This handle can be resolved by performing a Webfinger lookup via
GET https://domain.tld/.well-known/webfinger?resource=acct:username@domain.tld\n the activity pub object is then contained in the link with type application/activity+json.
The second method, that is less commonly used, is to provide a HTML link header of the form
<link\n href=\"https://activty.pub/object/id\"\n rel=\"alternate\"\n type=\"application/activity+json\"\n/>\n A FediVerse object receiving this HTML as a response to a lookup, can then parse it and use the link to lookup the corresponding object.
"},{"location":"fep/612d/#content-negotiation","title":"Content Negotiation","text":"On a server supporting content negotiation, a request with Accept header \"application/activity+json\" will be either served or redirected to the corresponding ActivityPub object. The author does not a method to identify where the ActivityPub object is, as it is being directly served.
"},{"location":"fep/612d/#example-of-using-dns-to-identify-an-activitypub-object","title":"Example of using DNS to identify an ActivityPub object","text":"The following shows an example configuration for the domain mymath.rocks correspond to the actor with FediVerse handle @helge@mymath.rocks, i.e. the author,
$ dig _apobjid.mymath.rocks\n\n;; ANSWER SECTION:\n_apobjid.mymath.rocks. 7200 IN TXT \"https://mymath.rocks/endpoints/SYn3cl_N4HAPfPHgo2x37XunLEmhV9LnxCggcYwyec0\"\n The corresponding object being
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\"],\n \"id\":\"https://mymath.rocks/endpoints/SYn3cl_N4HAPfPHgo2x37XunLEmhV9LnxCggcYwyec0\",\n \"name\":\"Helge\",\n \"preferredUsername\":\"helge\",\n \"summary\":\"<p>I like Math, cows, and wrote <a href=\\\"https://codeberg.org/bovine/bovine/\\\">bovine</a>.</p>\",\n \"type\":\"Person\", ...\n}\n"},{"location":"fep/612d/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/61cf/","title":"Index","text":"hide: - navigation description: \"OpenWebAuth is the \"single sign-on\" mechanism used by Hubzilla, (streams) and other related projects. It allows a browser-based user to log in to services across the Fediverse using a single identity. Once logged in, they can be recognised by other OpenWebAuth-compatible services, without third-party cookies and often without any explicit user interaction. This is not a specification, a proposal, or a \"best practice\" document. The aim is to describe the existing protocol in detail as an aid to implementers, evaluators, and anyone who wants to understand its operation. It is mostly based on reverse-engineering the existing implementations and focuses on the minimal requirements for basic interoperability. In OpenWebAuth, each user is identified by a public/private key pair. The protocol relies on there being a mechanism for other nodes on the network to discover a user's public key. This document assumes that ActivityPub actor objects will be used for this purpose. OpenWebAuth can also work with other protocols such as Zot6 and Nomad but these are not considered here.\"
"},{"location":"fep/61cf/#fep-61cf-the-openwebauth-protocol","title":"FEP-61cf: The OpenWebAuth Protocol","text":"Authors Status Type Date received Tracking issue Discussions Repository FenTiger @FenTiger@zotum.netDRAFT implementation 2024-02-06 #263 - codeberg OpenWebAuth is a federated remote authentication protocol. It can be used alongside protocols such as ActivityPub and Zot to add single sign-on to the Fediverse or to other \"social web\" projects such as blogs.
"},{"location":"fep/61cf/#summary","title":"Summary","text":"OpenWebAuth is the \"single sign-on\" mechanism used by Hubzilla, (streams) and other related projects. It allows a browser-based user to log in to services across the Fediverse using a single identity. Once logged in, they can be recognised by other OpenWebAuth-compatible services, without third-party cookies and often without any explicit user interaction.
This is not a specification, a proposal, or a \"best practice\" document. The aim is to describe the existing protocol in detail as an aid to implementers, evaluators, and anyone who wants to understand its operation. It is mostly based on reverse-engineering the existing implementations and focuses on the minimal requirements for basic interoperability.
In OpenWebAuth, each user is identified by a public/private key pair. The protocol relies on there being a mechanism for other nodes on the network to discover a user's public key. This document assumes that ActivityPub actor objects will be used for this purpose. OpenWebAuth can also work with other protocols such as Zot6 and Nomad but these are not considered here.
"},{"location":"fep/61cf/#overview","title":"Overview","text":"The protocol takes place between two participants:
The home instance, which hosts the user's identity, and is equivalent to the Identity Provider (IdP) in SAML and OpenID Connect.
The target instance, which allows remote users to log in to it using the provided identity. This is equivalent to the Relying Party (RP) in SAML and OpenID Connect.
The OpenWebAuth login flow can begin in one of two ways:
The user visits the target instance and sees a login screen. They type their Fediverse ID into a form field and click \"Login\".
The user follows a link to the target instance. This link has a query parameter, zid=, which specifies the user's Fediverse ID.
This zid= mechanism is not necessarily specific to OpenWebAuth. It can be used to start an OAuth2-based login flow in just the same way. Implementers should note, though, that it can make attacks easier to launch; an attacker who can trick a victim into clicking a malicious link can cause them to start the login flow using a maliciously constructed identity, perhaps leading to a MixUpAttack.
The protocol can be summarised in a sequence diagram as below. This diagram shows the zid= case, where the user is not prompted to log in.
sequenceDiagram\n participant browser as Browser\n participant target as Target instance\n participant home as Home instance\n\n browser ->> target: GET /page?zid=user@home\n target ->> home: webfinger user@home\n home -->> target: location of redirection endpoint\n target -->> browser: Location: https://home.example/magic?...\n browser ->> home: GET /magic?...\n Note over home: Check user is logged in,<br/>eg by checking session cookie\n home ->> target: webfinger /\n target -->> home: location of token endpoint\n rect rgb(216, 255, 216)\n Note over home,target: Protected by actor's private key\n home ->> target: GET /token<br/>(signed)\n target -->> home: <token><br/>(encrypted)\n end\n home -->> browser: Location: https://target.example/page?owt=<token>\n browser ->> target: GET /page?owt=<token>\n target -->> browser: <contents of page> Regardless of how the flow is started, the protocol begins with the user's browser making a request to the target instance.
"},{"location":"fep/61cf/#1-redirection-to-home-instance","title":"1. Redirection to home instance","text":"First the target instance identifies the home instance's \"redirection endpoint\".
Some existing implementations hard-code this to /magic. New implementations should perform a webfinger lookup on the provided user ID looking for a link with rel set to http://purl.org/openwebauth/v1#redirect. If found, this link's href should be used as the redirection endpoint.
The target instance constructs a URL from the redirection endpoint with the following query parameters: - owa: must be set to 1 - bdest: The URL which the browser will be returned to after acquiring a token. This is encoded as UTF-8 and then converted to a hexadecimal string. This is equivalent to the redirect_uri in OAuth2. The bdest URL can include query parameters.
The user's browser is redirected to this URL. The target instance should check that the URL has the same origin as the webfinger ID, to avoid acting as an open redirector.
"},{"location":"fep/61cf/#2-home-instance-requests-a-token","title":"2. Home instance requests a token","text":"The /magic endpoint at the user's home instance first checks that the user's browser has a valid session cookie.
If so, it decodes the bdest destination URL. It performs a webfinger lookup on the root URL of the destination site and looks for a link with rel set to http://purl.org/openwebauth/v1. This identifies the target instance's \"token endpoint\".
If an error occurs during this step, the home instance should not redirect to the 'bdest' URL; this would allow it to be used as an open redirector. Instead it should respond with a suitable HTTP error code.
On success, the home instance constructs and issues a signed HTTPS request to the discovered token endpoint. The request also contains an additional signed header, X-Open-Web-Auth, containing a random string. Target instances do not use this header; it is provided to add additional entropy to the signature calculation.
The target instance's token endpoint extracts the keyId, fetches the actor record, extracts the public key and verifies the signature.
On success, it generates an URL-safe random string to use as a token. This token is stored locally, associated with the actor who signed the message. The token is also encrypted using the actor's public key and the RSA PKCS #1 v1.5 encryption scheme. The encrypted result is encoded as URL-safe Base64 with no '=' padding bytes.
Next it constructs the following JSON object in response:
{\n \"success\": true,\n \"encrypted_token\": <the base64-encoded token>\n}\n On failure it can also return a result with success set to false.
The signed request issued in step 2 completes. The home instance decodes the JSON response and verifies that success is true. Next it decodes the Base64-encoded encrypted token and decrypts it using the actor's private key.
If successful, it takes the bdest destination URL, adds the query parameter: owt=<decrypted token>, and redirects the user's browser to it.
The user arrives back at the target instance. The target instance sees the owt= query parameter and checks its local storage for the token which it saved in step 3.
If found, this token identifies the remote user, and the target instance logs them in, overriding any existing login they may have. The token is also deleted from local storage so that it cannot be redeemed more than once.
"},{"location":"fep/61cf/#what-next","title":"What Next?","text":"(This section is non-normative.)
What does the target instance allow the user to do, now that they have signed in? The simplest option is to treat them as though they had signed in using a local username and password, but this isn't really enough.
In an ActivityPub context, a remotely authenticated user will expect to be able to post to the target instance using their existing identity. This poses the question of how the target instance should sign the post, since it does not have access to the user's private key. Hubzilla solves this by attributing the post to the user's identity, but signing it with a key belonging to the target instance; any other instance which receives the post must be aware of this mechanism, or the signature will be rejected.
"},{"location":"fep/61cf/#additional-notes","title":"Additional notes","text":""},{"location":"fep/61cf/#http-signatures","title":"HTTP Signatures","text":"An OpenWebAuth signed request must have an Authorization header starting with the word Signature followed by the encoded HTTP signature. See ActPubSig for more details on signing Fediverse requests.
To support logged in users, the target instance needs some logic to identify their requests. Normally this is done by checking for a valid session cookie. To support OpenWebAuth this logic must be extended to also check for the zid= and owt= query parameters.
Some corner cases are possible here. For instance, the user could already be logged in to the target instance when the OWA login flow begins.
When the OpenWebAuth flow succeeds, the owt= token will identify the logged-in user. It is important that the target instance trusts this token rather than the original zid= query parameter, to prevent an impersonation attack.
This endpoint should accept both GET and POST requests. Some home instances will issue POSTs with random bodies.
"},{"location":"fep/61cf/#home-instances-magic-endpoint","title":"Home instance's/magic endpoint","text":"The implementation of this endpoint needs to request a login token from the target instance. This requires it to prove possession of the user's private key, first to calculate a signature for the request and then to decrypt the returned token. These are the only places in the protocol where the private key is needed, implying that only the home instance needs to be a Fediverse instance. The target instance only needs access to public keys, meaning that OpenWebAuth can be used to allow users to log into things that are not instances.
"},{"location":"fep/61cf/#implementations","title":"Implementations","text":"The purpose of OpenWebAuth is to provide a strong guarantee of a user's identity to the web sites that they visit. This is often considered undesirable and consideration should be given to preventing this information from leaking to sites which may not be acting in the user's best interests.
This consideration may involve policies such as displaying a consent screen to the user or otherwise allowing them to choose which target instances they are willing to authenticate themselves to. The user's browser is redirected to their home instance at step 2, giving it an opportunity to implement policies such as these.
"},{"location":"fep/61cf/#denial-of-service-attack","title":"Denial-of-service attack","text":"Unused owt= login tokens are deleted after a couple of minutes. This protects against a potential DoS attack which could fill up the target instance's storage with unused tokens.
Suppose Mallory is logged into his home instance as mallory@example.social. It is easy for him to manually navigate to a URL on Alice's instance which looks like https://alice.social/somepage?zid=bob@example.social. When the OpenWebAuth flow completes, Mallory will return to Alice's instance with an owt= token identifying him as mallory@example.social. It is important that Alice's instance trusts this owt= token. If it mistakenly trusts the original zid= parameter, Mallory will be able to impersonate Bob.
OpenWebAuth involves redirecting the user's browser twice. It is important to verify the relevant URLs to avoid an OpenRedirectionAttack.
The first redirection sends the user to their home instance's redirection endpoint (often /magic). If this endpoint is discovered via webfinger, the target instance should check that its hostname portion matches the hostname in the user's claimed ID.
The second redirection sends the user back to the URL specified in the bdest query parameter. This redirection should only take place if * the token endpoint discovery step succeeded * the discovered token endpoint URL has the same origin as the bdest URL
In any other case the home instance's redirection endpoint should return a suitable HTTP error code.
"},{"location":"fep/61cf/#mixup-attack","title":"Mixup attack","text":"The zid= query parameter may make a MixUpAttack easier to launch, as discussed briefly above. This does not apply to OpenWebAuth but should be considered carefully when trying to generalise the zid= mechanism to other authentication protocols.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/6481/","title":"FEP-6481: Specifying ActivityPub extension support with NodeInfo","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository James Smith james@floppy.org.ukWITHDRAWN 2024-03-12 2024-10-31 #279 - codeberg"},{"location":"fep/6481/#summary","title":"Summary","text":"Many FediVerse services extend ActivityPub and ActivityStreams to add their own behaviour, such as custom object types. In order to interoperate with other servers running different software, the service needs to know whether or not the remote server supports these same extensions.
This FEP defines a standard method of specifying support for particular extensions in the server's NodeInfo file (as described in FEP-f1d5), so that compatibility information can be automatically discovered.
"},{"location":"fep/6481/#history","title":"History","text":"Many FediVerse software platforms such as Bookwyrm and Pixelfed have used the NodeInfo software field to determine if a remote server is running the same software, and is therefore compatible with their extensions. This works, however it restricts full interoperability with different software that could potentially use the same types.
The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in RFC-2119.
"},{"location":"fep/6481/#declaring-extension-support","title":"Declaring extension support","text":"FediVerse software that extends ActivityPub SHOULD indicate extension support in its NodeInfo file.
Extension identifiers MUST be valid IRIs. IRIs MAY be created with the w3id.org Permanent Identifier service, and if so they SHOULD redirect to a document that describes the extension.
To comply with this FEP, extensions MUST be specified in the metadata section of the NodeInfo file, as shown below:
{\n \"version\": \"2.2\",\n ...,\n \"protocols\": [\"activitypub\"],\n ...,\n \"metadata\": {\n \"activitypub\": {\n \"extensions\": [\n \"https://w3id.org/my-project/my-extension#v1\"\n ]\n }\n }\n}\n Note: The metadata key activitypub is chosen to match the string in protocols, and is refers to extensions in any of ActivityPub, ActivityStreams and the Activity Vocabulary.
It is RECOMMENDED that extension developers specify version information in their IRIs, for instance in a fragment identifier as above.
Once defined, IRIs MUST be constant - they cannot be changed while retaining compatibility. For this reason, it is RECOMMENDED to use a long-term service (e.g. w3id.org) for IRIs.
"},{"location":"fep/6481/#defining-iris","title":"Defining IRIs","text":"No specific recommendation is made for IRI design, although if using w3id.org, their Naming Policy should be adhered to.
"},{"location":"fep/6481/#examples","title":"Examples","text":"This section is non-normative.
Manyfold intends to define a new Activity Vocabulary Object type for its own rich content, 3dModel. The specification will define an extension IRI, for instance https://w3id.org/manyfold/3dModel#v1.
Applications that wish to receive Activities with this type can state their support for the extension in their NodeInfo file as follows; Manyfold instances will then know that they can send that object type and it will be understood.
{\n ...,\n \"metadata\": {\n \"activitypub\": {\n \"extensions\": [\n \"https://w3id.org/manyfold/3dModel#v1\"\n ]\n }\n }\n}\n A similar (entirely theoretical) example for Bookwyrm's Review object type might be: {\n ...,\n \"metadata\": {\n \"activitypub\": {\n \"extensions\": [\n \"https://joinbookwyrm.org/ns/activitypub#Review\"\n ]\n }\n }\n}\n"},{"location":"fep/6481/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/6606/","title":"FEP-6606: ActivityPub client to server collections addressing conventions","text":"Authors Status Date received Tracking issue Discussions Repository Marius Orcsik marius@federated.idDRAFT 2024-12-04 #453 Discussions codeberg"},{"location":"fep/6606/#summary","title":"Summary","text":"This document tries to describe a simple set of conventions to better enable the adressing of [ActivityPub] objects on servers that support [Client to Server Interactions]. Its main purpose is to formalize a basic vocabulary for defining subsets of IRIs [RFC-3987] for collections in a way that can be generalized to both servers and clients. It builds upon the definition of query parametrs [RFC-3986], by introducing a set of additional operators that can be applied to values.
"},{"location":"fep/6606/#what-we-are-trying-to-solve","title":"What we are trying to solve","text":"The main reason that is often cited as making client to server interactions problematic is that the official specification leaves too much to the implementors' discretion. The fact that there is no proscribed method of filtering collections content is one of the stop gaps that prevents a wider adoption of C2S in services and clients.
"},{"location":"fep/6606/#syntax","title":"Syntax","text":"The following syntax is extracted form RFC-3986 section 3.4, and it formalizes the URL query parameters as representing any string that conforms to the restrictions below between the first \"?\" character and the end of the URL or a \"#\" character marking the start of the fragment part of the URL.
query = *( pchar / \"/\" / \"?\" )\n\npchar = unreserved / pct-encoded / sub-delims / \":\" / \"@\"\n\nunreserved = ALPHA / DIGIT / \"-\" / \".\" / \"_\" / \"~\"\n\npct-encoded = \"%\" HEXDIG HEXDIG\n\nsub-delims = \"!\" / \"$\" / \"&\" / \"'\" / \"(\" / \")\" / \"*\" / \"+\" / \",\" / \";\" / \"=\"\n As we can see this is not formal definition of the query string as a group of key and value pairs, but for our intended use, this is paramount.
So we will take this extra step ourselves and extend the definition to be:
query = *( query-pair [ qconcat ] )\n\nquery-pair = pname [ \"=\" ( pvalue ) ]\n\npname = 1*( pchar )\n\npvalue = *( pchar )\n\npchar = unreserved / pct-encoded / new-delims / \":\" / \"@\"\n\nqconcat = \"&\"\n\nunreserved = ALPHA / DIGIT / \"-\" / \".\" / \"_\" / \"~\"\n\npct-encoded = \"%\" HEXDIG HEXDIG\n\nnew-delims = \"!\" / \"$\" / \"'\" / \"(\" / \")\" / \"*\" / \"+\" / \",\" / \";\"\n To allow for the operators that we want to append to the query values we extend this version with the following elements:
query-pair = p-name [ \"=\" ( [ p-operator ] p-value ) ]\n\np-operator = \"!\" / \"~\"\n\np-value = empty-value / *( pchar )\n\nempty-value = \"-\"\n The operator symbols are \"!\" and \"~\" that correspond to negation respectively fuzzy matching of the query value.
Explicitly, when encountering a URL parameter value that has a \"!\" symbol in front of it we mean it as \"different than\". Similarly when encountering a value prepended with the \"~\" symbol, we mean it as \"similar with\" in a textual manner.
Additionally we added a nil value symbol \"-\" that can be used for operations where the value to compare against is absent.
Please look at the following examples to see how these rules apply and compound with one another:
?element=value\n// resources matching exactly \"value\"\n\n?element=1&element=2\n// resources matching exactly \"1\" or \"2\"\n\n?element=!value1\n// resources inversly matching \"value1\"\n\n?element=!1&element=!2\n// resources inversly matching \"1\" and \"2\"\n\n?element=~fuzzy\n// resources fuzzy matching \"fuzzy\"\n\n?element=~one&element=~two\n// resources fuzzy matching \"one\" or \"two\"\n\n?element=-\n// resources matching empty element values\n\n?element=!-\n// resources matching all non empty element values\n"},{"location":"fep/6606/#applicability-for-activitypub-collections","title":"Applicability for ActivityPub Collections","text":"In this ection we will define how we are going to apply this newly defined schema to use URL query parameters to filter ActivityPub collections.
We will assume a random ActvityPub collection can be found at the folowing URL:
https://example.com/random-collection\n The response to a GET request to this IRI could return something like:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/random-collection\",\n \"name\": \"Random Collection\",\n \"type\": \"OrderedCollection\",\n \"updated\": \"2021-04-09T08:16:05Z\",\n \"first\": \"https://example.com/random-collection?maxItems=10\",\n \"totalItems\": 12,\n \"orderedItems\": [ /* skipping items for brevity */ ]\n}\n The filtering for the elements in the collection will be done by using URL query parameters with the names corresponding to the different properties that we want to filter on for the objects in the collection.
Example:
https://example.com/random-collection?type=Place\n\n// will return only objects with the type `Place` that exist in the\n// Random Collection.\n\nhttps://example.com/random-collection?summary=~test%20example\n\n// will return only objects containing a summary property that contains\n// the \"test example\" text.\n"},{"location":"fep/6606/#implementations","title":"Implementations","text":"The grammar described in this document represents a simplified version of the mechanism that services using the [GoActivityPub] library use for their C2S addressing.
"},{"location":"fep/6606/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/67ff/","title":"FEP-67ff: FEDERATION.md","text":"Authors Status Date received Date final Tracking issue Discussions Repository silverpill @silverpill@mitra.socialFINAL 2023-09-05 2024-09-22 #157 Discussions codeberg"},{"location":"fep/67ff/#summary","title":"Summary","text":"FEDERATION.md is a file containing information necessary for achieving interoperability with a federated service. It was originally proposed by Darius Kazemi on SocialHub forum in Documenting federation behavior in a semi-standard way? topic.
The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/67ff/#structure","title":"Structure","text":"The FEDERATION.md file can have arbitrary structure and content. The only requirements are:
FEDERATION.md file may contain a link to that location.(This section is non-normative.)
# Federation\n\n## Supported federation protocols and standards\n\n- [ActivityPub](https://www.w3.org/TR/activitypub/) (Server-to-Server)\n- [WebFinger](https://webfinger.net/)\n- [Http Signatures](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures)\n- [NodeInfo](https://nodeinfo.diaspora.software/)\n\n## Supported FEPs\n\n- [FEP-67ff: FEDERATION.md](https://codeberg.org/fediverse/fep/src/branch/main/fep/67ff/fep-67ff.md)\n\n## ActivityPub\n\n<!-- Describe activities and extensions. -->\n\n## Additional documentation\n\n<!-- Add links to documentation pages. -->\n"},{"location":"fep/67ff/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/6fcd/","title":"FEP-6fcd: Account Export Container Format","text":"Authors Status Date received Tracking issue Discussions Repository Dmitri Zagidulin @dmitri@social.coopDRAFT 2024-07-11 #357 Discussions codeberg"},{"location":"fep/6fcd/#summary","title":"Summary","text":"This FEP describes a lightweight general purpose account export container format, with the following properties:
Out of scope:
.tar file into a .tar.gz is well known)manifest.yml file that describes what's in the files and directories.tar file.ubc-version: Version of the Account Export Container Format speccontents: A listing of files and directories in this .tar filemeta: A metadata section describing who this export belongs to, what app or service created it, and so on.Example result of exporting an ActivityPub account:
$ tar -vtf ap-account-export-2024-06-11.tar\n\n-rw-rw-r-- 0 0 1K Jun 11 15:38 manifest.yml\ndrwxrwxr-x 0 0 4.0K Jun 11 15:38 activitypub/\ndrwxrwxr-x 0 0 4.0K Jun 11 15:38 key/\n Example corresponding manifest.yml file:
# (Required) Universal Backup Container spec version\nubc-version: 0.1\n\n# (Optional) Metadata section\nmeta:\n created: 2024-01-01\n createdBy:\n # (Optional) URL to a Controller document, such as an ActivityPub profile using FEP-521a\n # @see https://codeberg.org/fediverse/fep/src/branch/main/fep/521a/fep-521a.md\n controller: https://alice-personal-site.example/actor\n # (Optional) The app or service that created this export\n client:\n name: \"Example Exporter App\"\n url: https://codeberg.example.com/example-export-app\n\n# (Required, but can be empty) Contents section, listing the other files and directories\ncontents:\n # This file\n manifest.yml:\n url: https://codeberg.org/fediverse/fep/src/branch/main/fep/6fcd/fep-6fcd.md#manifest-file\n # Directory with ActivityPub-relevant exports\n activitypub:\n contents:\n # Serialized ActivityPub Actor profile\n actor.json:\n url: https://www.w3.org/TR/activitypub/#actor-objects\n # ActivityStreams OrderedCollection representing the contents of the actor's Outbox\n outbox.json:\n url: https://www.w3.org/TR/activitystreams-core/#collections\n following_accounts.csv:\n url: https://docs.joinmastodon.org/user/moving/#export\n followers.csv:\n url: https://docs.joinmastodon.org/user/moving/#export\n lists.csv:\n url: https://docs.joinmastodon.org/user/moving/#export\n bookmarks.csv:\n url: https://docs.joinmastodon.org/user/moving/#export\n blocks.csv:\n url: https://docs.joinmastodon.org/user/moving/#export\n mutes.csv:\n url: https://docs.joinmastodon.org/user/moving/#export\n # Directory of object attachments (post images, etc)\n attachments:\n url: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-attachment\n contents:\n # Actor profile avatar\n avatar.jpg:\n url: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-icon\n # 'key' dir, serialized private/public key pairs,\n # such as those declared in a FEP-521a Actor profile\n key:\n url: https://codeberg.org/fediverse/fep/src/branch/main/fep/521a/fep-521a.md\n contents:\n key-1234.json:\n url: https://www.w3.org/TR/vc-di-eddsa/#representation-eddsa-rdfc-2022\n Example exported key file:
$ cat key/key-1234.json\n {\n \"@context\": [\"https://w3id.org/security/multikey/v1\"],\n \"type\": \"Multikey\",\n \"id\": \"https://alice-personal-site.example/actor#key1234\",\n \"controller\": \"https://alice-personal-site.example/actor\",\n \"publicKeyMultibase\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\",\n \"privateKeyMultibase\": \"z3u2en7t5LR2WtQH5PfFqMqwVHBeXouLzo6haApm8XHqvjxq\"\n}\n"},{"location":"fep/6fcd/#example-blog-archive-format-export","title":"Example Blog Archive Format Export","text":"$ tar -vtf bar-account-export-2024-06-11.tar\n\n-rw-rw-r-- 0 0 1K Jun 11 15:38 manifest.yml\n-rw-rw-r-- 0 0 100K Jun 11 15:38 index.html\n-rw-rw-r-- 0 0 50K Jun 11 15:38 feed.json\ndrwxrwxr-x 0 0 4.0K Jun 11 15:38 uploads/\n Example corresponding manifest.yml file:
ubc-version: 0.1\n\nmeta:\n created: 2024-01-01\n\ncontents:\n # This file\n manifest.yml:\n url: https://codeberg.org/fediverse/fep/src/branch/main/fep/6fcd/fep-6fcd.md#manifest-file\n index.html:\n url: https://indieweb.org/blog_archive_format\n feed.json:\n url: https://indieweb.org/blog_archive_format\n uploads:\n url: https://indieweb.org/blog_archive_format\n"},{"location":"fep/6fcd/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/73cd/","title":"FEP-73cd: Migration User Stories","text":"Authors Status Date received Tracking issue Discussions Repository Bumblefudge <@learningproof.xyz> / @by_caballero@mastodon.socialDRAFT 2024-02-07 #265 - codeberg"},{"location":"fep/73cd/#summary","title":"Summary","text":"In the interest of clarifying and aligning on the problem-space of user account migration, multiple-account management, and export/import/migration of content/activity history, these user stories are offered to organize discussion and solution-sharing.
"},{"location":"fep/73cd/#contributing","title":"Contributing","text":"Any one of these user stories could be further elaborated in the spirit of the SWAT exercise that served as ActivityPub's initial \"definition of done\", perhaps in a separate FEP and/or SocialHub thread.
"},{"location":"fep/73cd/#migration-user-stories","title":"Migration User Stories","text":"PRs welcome! Add or update!
1, 2, and 3 describe follower/profile migration, while 4,5,6 and 7 describe migration of posted activities.
Note: this section is largely subjective and intended as an aid to decision-making, NOT as any kind of definitive statement about the utility or necessity of other FEPs. Feel free to PR in links to blog posts or other tutorials that explain a given implementation of any user stories to the \"profile\" row in the form [1](<link>), [2](<link>)...
push mode) R1 R1 R FEP-7628 \"Move Actor\" (pull mode) R2 R FEP-c390 \"Identity Proofs\" H H R FEP-ae97 \"Client-Side Activity Signing\" R FEP-ef61 \"Portable Objects\" R R H H FEP-8b32 \"Object Integrity Proofs\" (req'd by ^) R^ R^ H^ H^ FEP-521a \"Actor's Public Keys\" H H H H FEP-TBD [Per-user? Per-server? both?] \"Moderation Records\" R H FEP-TBD \"Forwarding and Redirecting Migrated Actors\" H R R? H H R FEP-TBD \"Activity Archives/Export Format\" R R Profiles needed to combine the above?"},{"location":"fep/73cd/#open-questions","title":"Open Questions","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/7458/","title":"FEP-7458: Using the replies collection","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT 2024-04-26 #307 - codeberg"},{"location":"fep/7458/#summary","title":"Summary","text":"Replies are not defined in ActivityPub, as there are no specified side effects for simply encountering objects or activities with inReplyTo set. Most current implementations implicitly reconstruct replies at consumption time, and may silently and implicitly maintain the replies collection at the producer's side. This FEP provides guidance for explicitly using the replies collection, and explicitly managing it.
(This section is non-normative.)
ActivityPub defines side effects for activities, not side effects for encountering objects. One popular use-case in the social web is replying to an object with your own object; this indicates that your object is a response to the other object. However, this is about as far as most implementations go in maintaining the \"reply\" relation. It is generally assumed that objects will be delivered to other servers via a Create activity, and that the object of that Create activity will be cached, and that the \"reply\" will somehow be tracked and attached to the referent object. The ActivityPub spec only mentions \"replies\" in one place, in the context of inbox forwarding as intended to avoid the problem of \"ghost replies\". The implication of this is that senders are expected to address all relevant parties ahead-of-time, as a matter of convention.
What is missing is a mechanism for managing replies that would recognize the canonicity and authority of the actor managing their own replies explicitly, rather than depending on implicit reconstruction after-the-fact. FEP-5624 takes a different approach that doubles down on this implicit reconstruction.
Meanwhile, FEP-7888 specifies a similar mechanism for managing contexts that recognizes the canonicity and authority of the actor managing the conversation explicitly, rather than depending on implicit reconstruction after-the-fact. Implicit reconstruction is usually based on reply trees, but explicit construction is based on Add and Remove activities. The current FEP aims to be written in a way that supports explicit replies management while being orthogonal to the use of context. In other words, the authoring actor has the option to use inReplyTo as metadata, while separately choosing to participate in a context or not.
(This section is non-normative.)
Alice makes a post:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://alice.example/outbox/1\",\n \"summary\": \"Alice created a note\",\n \"actor\": \"https://alice.example\",\n \"type\": \"Create\",\n \"object\": {\n \"id\": \"https://alice.example/objects/1\",\n \"type\": \"Note\",\n \"content\": \"Hello\",\n \"replies\": \"https://alice.example/objects/1/replies\"\n },\n \"cc\": [\"https://alice.example/followers\", \"as:Public\"]\n}\n Bob makes a reply:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://bob.example/outbox/2\",\n \"summary\": \"Bob replied to Alice's note\",\n \"actor\": \"https://bob.example\",\n \"type\": \"Create\",\n \"object\": {\n \"id\": \"https://bob.example/objects/2\",\n \"type\": \"Note\",\n \"content\": \"Hi!\",\n \"inReplyTo\": \"https://alice.example/objects/1\"\n },\n \"to\": \"https://alice.example\",\n \"cc\": [\"https://alice.example/followers\", \"https://bob.example/followers\"]\n}\n Alice explicitly approves Bob's reply, either automatically or manually. Alice forwards Bob's activity to Alice's followers, as requested by Bob. Alice also adds Bob's reply to the replies collection:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://alice.example/outbox/3\",\n \"summary\": \"Alice added Bob's note as a reply\",\n \"actor\": \"https://alice.example\",\n \"type\": \"Add\",\n \"object\": \"https://bob.example/objects/2\",\n \"target\": \"https://alice.example/objects/1/replies\",\n \"to\": \"https://bob.example\",\n \"cc\": [\"https://alice.example/followers\", \"https://bob.example/followers\"]\n}\n Bob sees this and updates the original reply to something offensive:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://bob.example/outbox/4\",\n \"summary\": \"Bob updated a note\",\n \"actor\": \"https://bob.example\",\n \"type\": \"Update\",\n \"object\": {\n \"id\": \"https://bob.example/objects/2\",\n \"type\": \"Note\",\n \"content\": \"Alice sucks lol\",\n \"inReplyTo\": \"https://alice.example/objects/1\"\n },\n \"to\": \"https://alice.example\",\n \"cc\": [\"https://alice.example/followers\", \"https://bob.example/followers\"]\n}\n Perhaps Alice forwards this activity automatically, or perhaps not; at some later point, Alice notices that the reply has been edited. Alice therefore decides to remove the reply:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://alice.example/outbox/5\",\n \"summary\": \"Alice removed a reply\",\n \"actor\": \"https://alice.example\",\n \"type\": \"Remove\",\n \"object\": \"https://bob.example/objects/2\",\n \"target\": \"https://alice.example/objects/1/replies\",\n \"to\": \"https://bob.example\",\n \"cc\": [\"https://alice.example/followers\", \"https://bob.example/followers\"]\n}\n Bob maliciously chooses not to forward this removal activity.
Charlie follows Bob, but not Alice. Charlie's client has received activities 2, 3, and 4 in Charlie's inbox.
Under current behavior, Charlie would have fetched Alice's post, but not have checked Alice's replies collection or used it in any way. Bob's reply would still be visible as a reply to Alice's post.
Under proposed behavior, Charlie would instead defer to checking Alice's replies collection. Bob's reply would not be contained in that collection, as Alice has removed it. Therefore, Charlie does not see or treat Bob's reply as a verified reply.
inReplyTo set, ActivityPub clients SHOULD verify that the current object is indeed a valid member of the inReplyTo.replies collection. (A further FEP may provide a mechanism to establish proof of this without having to check the entire collection.)inReplyTo is set to some object attributedTo the current actor, an ActivityPub actor SHOULD send an activity with the following properties:type array MUST include Addobject MUST be the object declaring an inReplyTotarget MUST be your own object's replies collectionobject.attributedTo actor, as well as potentially their followers, in addition to any other addressed actors (such as your own followers or some other audience)replies collection that they own by sending an activity with the following properties:type array MUST include Removeobject MUST be the object declaring an inReplyTotarget MUST be your own object's replies collectionobject.attributedTo actor, as well as potentially their followers, in addition to any other addressed actors (such as your own followers or some other audience)CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/7502/","title":"FEP-7502: Limiting visibility to authenticated actors","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT 2023-12-24 #218 - codeberg"},{"location":"fep/7502/#summary","title":"Summary","text":"Some servers require authentication for all requests made via ActivityPub, even for GET requests on public objects addressed to as:Public. This violates the requirement that anything addressed to as:Public is made available without requiring authentication. This FEP proposes an alternative addressing that may be used in such scenarios, signaling that the object is not fully public but is otherwise available to any actor.
(This section is non-normative.)
ActivityPub issue 339 discusses the shortcomings of as:Public and proposed a new delivery/addressing target of as:Authenticated, but this proposal was never adopted. The distinction between the two is as follows:
Public : Available to anyone without authentication
Authenticated : Available to any actor after authentication
Issues raised with this approach include:
The concern with semantic clarity is unaddressed, as replacing as:Public with a different mechanism to signal fully public objects is out-of-scope for this FEP. However, this mechanism is still useful for addressing different concerns:
In WAC, there is a distinction made between an \"agent\" (http://xmlns.com/foaf/0.1/Agent) and an \"authenticated agent\" (http://www.w3.org/ns/auth/acl#AuthenticatedAgent). This FEP proposes an addressing target for the latter.
In addition to collections and objects, Activities may additionally be addressed to a special \"authenticated\" collection, with the identifier http://www.w3.org/ns/auth/acl#AuthenticatedAgent.
Activities addressed to this special URI shall be accessible to all actors, with authentication. Implementations MUST NOT deliver to the \"authenticated\" special collection; it is not capable of receiving actual activities.
An activity SHOULD NOT be addressed to both \"public\" and \"authenticated\". In such a case, addressing to \"authenticated\" has no effect while also addressed to \"public\".
"},{"location":"fep/7502/#overview","title":"Overview","text":"to or cc or includes in the audience the \"authenticated agents\" special identifier.Pixelfed intends to implement this FEP when \"signed fetch\" is enabled, such that the resulting data returned after a signed fetch does not include as:Public.
http://www.w3.org/ns/auth/acl#AuthenticatedAgent Label Anyone authenticated Subclass of Agent Is defined by Basic Access Control ontology"},{"location":"fep/7502/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/7628/","title":"FEP-7628: Move actor","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT informational 2023-09-20 #168 Discussions codeberg"},{"location":"fep/7628/#summary","title":"Summary","text":"Migration of followers from one ActivityPub actor to another.
"},{"location":"fep/7628/#history","title":"History","text":"Move activity is defined in Activity Vocabulary.
Mastodon started using Move activity for migrating accounts in 2019. The activity is sent by the old server to actor's followers and actors who receive this activity un-follow the old account and follow the new account.
The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/7628/#linked-actors","title":"Linked actors","text":"One persona can be represented by one or more actors. Different actors are considered associated with one persona if and only if they are linked. Possible ways to link actors:
alsoKnownAs property of an actor object. This method is the most popular, it is used by Mastodon and many other ActivityPub servers.subject property.Among actors associated with a persona, there MUST be one primary actor. Migration of an account means the change of a primary actor.
"},{"location":"fep/7628/#move-activity","title":"Move activity","text":"Move activity is used to notify other actors about the migration. It MUST have the following properties:
type: the type of activity MUST be Move.actor: the actor performing the migration.object: the old primary actor ID.target: the new primary actor ID.The activity is considered valid if object and target actors are linked.
Upon receiving valid Move activity, all actors following object MAY un-follow it by sending Undo(Follow) activity and MUST either send Follow activity to the target or otherwise notify the user that their contact has moved.
This activity comes in two sub-types:
Move sent by the old actor (push mode). In this case, the old server needs to be online.Move sent by the new actor (pull mode). In this case, the old server doesn't need to be online, but the person using the actor needs to have a reserve copy of the follower list.Example (activity subtype 1, push mode):
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://server1.example/activities/9b496346-fa69-40ac-bc4e-7ac06192abe1\",\n \"type\": \"Move\",\n \"actor\": \"https://server1.example/users/alice\",\n \"object\": \"https://server1.example/users/alice\",\n \"target\": \"https://server2.example/users/alice\",\n \"to\": \"https://www.w3.org/ns/activitystreams#Public\",\n \"cc\": \"https://server1.example/users/alice/followers\"\n}\n Example (activity subtype 2, pull mode):
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://server2.example/activities/9b496346-fa69-40ac-bc4e-7ac06192abe1\",\n \"type\": \"Move\",\n \"actor\": \"https://server2.example/users/alice\",\n \"object\": \"https://server1.example/users/alice\",\n \"target\": \"https://server2.example/users/alice\",\n \"to\": \"https://www.w3.org/ns/activitystreams#Public\",\n \"cc\": \"https://server2.example/users/alice/followers\"\n}\n"},{"location":"fep/7628/#movedto-and-copiedto-properties","title":"movedTo and copiedTo properties","text":"If previous primary actor was deactivated after migration, it MUST have movedTo property containing the ID of the new primary actor. Publishers SHOULD NOT deliver activities to actor's inbox if movedTo property is present.
If previous primary actor is not deactivated, copiedTo property MUST be used.
After adding the movedTo or copiedTo property, the actor SHOULD notify its followers about the change by publishing an Update activity.
The location of the new primary actor. The actor containing this property should be considered inactive.
https://w3id.org/fep/7628#movedToThe location of the new primary actor.
https://w3id.org/fep/7628#copiedToCC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/76ea/","title":"FEP-76ea: Conversation Threads","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.orgDRAFT 2024-10-04 #407 Discussions codeberg"},{"location":"fep/76ea/#summary","title":"Summary","text":"This FEP defines a way to identify the conversation thread of an object with Activity Streams 2.0.
"},{"location":"fep/76ea/#motivation","title":"Motivation","text":"Threaded conversations are a common data structure for social software. This is defined as a tree with the original post at its root, replies to that post as child nodes, all replies to those replies as their children, and so on recursively.
Some social software restricts the depth of the thread, while others allow for unlimited depth.
Identifying the thread that an AS2 object is part of allows for the construction of a conversation view of the thread.
It is possible with Activity Streams 2.0 to construct a conversation thread by following the inReplyTo property of an object until the original post is found, and then expanding the replies property of the original post recursively. With ActivityPub, however, this can require a lot of different HTTPS requests to different servers, which can be slow and inefficient.
This FEP defines an extension property, thread, that can be used to identify the conversation thread of an object.
ActivityPub is the primary use case for Activity Streams 2.0, but not the only one. Where specific processing requirements of ActivityPub implementations are made, they are specifically noted. General processing hints for other use cases are also provided.
"},{"location":"fep/76ea/#user-stories","title":"User stories","text":"These are some user stories for threading in conversations.
inReplyTo chain and following the replies collections, but it can be slow and inefficient. Comparing a thread identifier found in each object can be much faster.inReplyTo chain and replies collections can be slow and inefficient, especially if the thread is deep or has many objects.inReplyTo and replies, and possibly requires fetching every single replies collection in the tree.replies collection is not sufficient, because it only contains direct replies to the object, not the full conversation tree.The context URL for this FEP is https://purl.archive.org/socialweb/thread.
The context is as follows:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"thr\": \"https://purl.archive.org/socialweb/thread#\",\n \"thread\": {\n \"@id\": \"thr:thread\",\n \"@type\": \"@id\"\n },\n \"root\": {\n \"@id\": \"thr:root\",\n \"@type\": \"@id\"\n }\n}\n"},{"location":"fep/76ea/#terms","title":"Terms","text":"The context defines two properties.
"},{"location":"fep/76ea/#thread","title":"thread","text":"The thread property is an OrderedCollection that contains all of the objects in the conversation thread. The collection is ordered in reverse chronological order, with the most recent object first.
The thread collection does not directly represent the tree structure of the conversation thread; it is a flat list of objects. The tree structure can be reconstructed by following the inReplyTo and/or replies properties of each object in the collection.
The thread property extends the context property from the Activity Vocabulary.
The thread property does not replace the replies property of an object. replies contains the possibly curated collection of direct replies to the object; thread contains the full conversation tree, up- and down-thread.
root","text":"The root property is an Object that is the original post of the conversation thread. The root property is usually the last (earliest) object in the thread collection.
This property gives an easy way for a consumer to find the root post of the thread without having to search the orderedItems collection, navigate through multiple OrderedCollectionPage pages, or traverse the inReplyTo properties of the objects in the collection.
Note that thread and root are partially inverse properties. The thread property of the root property of a collection SHOULD contain the id of the thread collection. However, the root property of the thread property of an object MAY not contain the object's id, because the object is in the thread, but is not the root.
This covers recommended behavior for processors that implement the thread property.
When a publisher creates a new content object that is not a reply to any others, it should include a new, unique collection as its thread property. The collection should contain only the new object. The thread collection should be addressed to all the same addressees as the original object.
When a publisher is creating a new content object with an inReplyTo property, the publisher SHOULD use the thread property of the object being replied to as the thread property of the new object. The addressees of the new object should include the creator of the original post, identified by the attributedTo property of the original post or the attributedTo property of the thread collection.
Replies can be created to multiple other objects; the inReplyTo property can be an array. The thread property can also be an array, with more or fewer values than the inReplyTo. Each thread property should correspond to the thread property of an object in the inReplyTo array.
To branch a content object into its own conversation thread, the publisher should create an Announce activity that includes the new object as the object property. The Announce activity should have a new, unique thread property. The Announce activity can include a content property.
To graft a content object into a different thread than the ones it is already part of, the publisher should create an Announce activity that includes the new object as the object property. The Announce activity should have the thread property of the new thread, and an inReplyTo property that matches one of the objects in the thread. The Announce activity can include a content property.
As with the replies property, the processor implementing the original post of a thread SHOULD maintain the thread collection by adding new objects to the collection as they are received.
In ActivityPub, this could be done when the processor receives an object with an inReplyTo property that matches an object in the thread collection.
To facilitate collection synchronization, the processor SHOULD distribute an Add activity to the audience of the original object with the new object as the object property and the thread as the target property.
However, private replies \"down-thread\" may not be addressed to the author of the original post and may not be available to the processor for the original post.
The processor implementing the original post MAY curate the thread collection by filtering objects from the collection. This could be done to remove spam, off-topic, or abusive content from the thread.
In ActivityPub, if an object is removed from the thread, he processor SHOULD distribute a Remove activity to the audience of the original object with the new object as the object property and the thread as the target property.
The tree structure of the thread should be maintained; every object in the thread collection, except the root, should have an inReplyTo property that matches the id of another object in the collection. If the processor removes an object from the collection, it SHOULD remove all objects that are in reply to that object, and their replies, and so on.
The replies property of objects in the thread collection MAY be maintained by other processors. Curation of the replies collections or of the thread collection may mean that objects may be omitted from one collection or the other. However, the replies collection of the original post SHOULD be a subset of the thread collection.
An example of a Note object with a thread property:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/note/123\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/user/1\",\n \"to\": [\n \"https://remote.example/user/17\",\n \"https://remote.example/user/17/followers\"\n ],\n \"content\": \"I concur!\",\n \"thread\": \"https://remote.example/thread/117\",\n \"inReplyTo\": \"https://remote.example/note/117\"\n}\n"},{"location":"fep/76ea/#example-2","title":"Example 2","text":"An example of an Image object with a thread property. The Image is a root or original post with no inReplyTo property:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/image/123\",\n \"type\": \"Image\",\n \"name\": \"A photo of a cat\",\n \"attributedTo\": \"https://example.com/user/1\",\n \"to\": \"https://example.com/user/1/followers\",\n \"url\": {\n \"type\": \"Link\",\n \"mediaType\": \"image/jpeg\",\n \"href\": \"https://example.com/image/123.jpg\"\n },\n \"replies\": \"https://example.com/replies/123\",\n \"thread\": {\n \"id\": \"https://example.com/thread/123\",\n \"to\": \"https://example.com/user/1/followers\",\n \"type\": \"OrderedCollection\",\n \"totalItems\": 4,\n \"orderedItems\": [\n {\n \"id\": \"https://fourth.example/note/721\",\n \"attributedTo\": \"https://fourth.example/user/4\",\n \"to\": [\n \"https://example.com/user/1\",\n \"https://example.com/user/1/followers\",\n \"https://other.example/user/2\"\n ],\n \"inReplyTo\": \"https://other.example/note/338\"\n },\n {\n \"id\": \"https://third.example/note/992\",\n \"attributedTo\": \"https://third.example/user/3\",\n \"to\": \"https://example.com/user/1\",\n \"inReplyTo\": \"https://example.com/image/123\"\n },\n {\n \"id\": \"https://other.example/note/338\",\n \"attributedTo\": \"https://other.example/user/2\",\n \"to\": [\n \"https://example.com/user/1\",\n \"https://example.com/user/1/followers\"\n ],\n \"inReplyTo\": \"https://example.com/image/123\"\n },\n \"https://example.com/image/123\"\n ]\n }\n}\n Note that not all objects in the thread collection need to be addressed to the same audience. The audience of the thread collection is the audience of the original post.
This is a Note object that is a reply to two different objects, and thus is part of two different threads.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/note/789\",\n \"attributedTo\": \"https://example.com/user/1\",\n \"to\": \"as:Public\",\n \"content\": \"These are both good points.\",\n \"inReplyTo\": [\n \"https://remote.example/note/57\",\n \"https://other.example/note/456\"\n ],\n \"thread\": [\n \"https://remote.example/thread/57\",\n \"https://other.example/thread/456\"\n ]\n}\n"},{"location":"fep/76ea/#example-4","title":"Example 4","text":"Objects in a thread that have been deleted by their author can be represented in the thread collection with a Tombstone object.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/note/345\",\n \"attributedTo\": \"https://example.com/user/1\",\n \"to\": \"as:Public\",\n \"content\": \"Activity Streams 2.0 is awesome!\",\n \"replies\": \"https://example.com/replies/345\",\n \"thread\": {\n \"id\": \"https://example.com/thread/345\",\n \"to\": \"as:Public\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n {\n \"id\": \"https://third.example/note/567\",\n },\n {\n \"type\": \"Tombstone\",\n \"id\": \"https://remote.example/note/456\",\n \"inReplyTo\": \"https://example.com/note/345\",\n \"deleted\": \"2024-10-03T00:00:00Z\"\n },\n \"https://example.com/note/345\"\n ]\n }\n}\n"},{"location":"fep/76ea/#example-5","title":"Example 5","text":"The thread collection can be paged, as with other collections.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/note/678\",\n \"attributedTo\": \"https://example.com/user/1\",\n \"to\": \"as:Public\",\n \"content\": \"Is Wario A Libertarian?\",\n \"replies\": \"https://example.com/replies/678\",\n \"thread\": {\n \"id\": \"https://example.com/thread/678\",\n \"to\": \"as:Public\",\n \"type\": \"OrderedCollection\",\n \"totalItems\": 244780,\n \"first\": \"https://example.com/thread/678/page/12239\",\n \"last\": \"https://example.com/thread/678/page/1\"\n }\n}\n"},{"location":"fep/76ea/#example-6","title":"Example 6","text":"The root property can be used to identify the original post of a thread.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/thread/654\",\n \"type\": \"OrderedCollection\",\n \"totalItems\": 457,\n \"first\": \"https://example.com/thread/654/page/23\",\n \"last\": \"https://example.com/thread/654/page/1\",\n \"root\": \"https://example.com/note/654\"\n}\n"},{"location":"fep/76ea/#example-7","title":"Example 7","text":"To branch an object to a new conversation, an Announce activity is used.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/announce/123\",\n \"to\": \"as:Public\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/user/1\",\n \"thread\": \"https://example.com/thread/123\",\n \"content\": \"I think this note is important and I want to start a separate discussion about it.\",\n \"object\": {\n \"id\": \"https://example.com/note/456\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.org/user/2\",\n \"thread\": \"https://example.net/thread/789\",\n \"inReplyTo\": \"https://example.net/note/foo\",\n \"to\": \"as:Public\",\n \"content\": \"Trains are great.\"\n }\n}\n"},{"location":"fep/76ea/#example-8","title":"Example 8","text":"To graft an object to an existing conversation, an Announce activity is used.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/thread\"\n ],\n \"id\": \"https://example.com/announce/456\",\n \"to\": \"as:Public\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/user/1\",\n \"thread\": \"https://social.example/thread/222\",\n \"inReplyTo\": \"https://social.example/note/888\",\n \"content\": \"This comment about trains from another thread seems relevant here.\",\n \"object\": {\n \"id\": \"https://example.com/note/456\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.org/user/2\",\n \"thread\": \"https://example.net/thread/789\",\n \"inReplyTo\": \"https://example.net/note/foo\",\n \"to\": \"as:Public\",\n \"content\": \"Trains are great.\"\n }\n}\n"},{"location":"fep/76ea/#security-considerations","title":"Security Considerations","text":"Not all objects in the thread collection may be addressed to the same audience. Representations of the collection SHOULD NOT include the content property or other sensitive information from objects in the collection that are not addressed to the recipient of the representation.
In ActivityPub, the orderedItems property of the thread collection MAY be filtered for the recipient of the representation.
The ostatus:conversation property is used in Mastodon and elsewhere to identify the thread of an object, but it is not necessarily dereferenceable.
Some implementations of ActivityPub use the context property to represent the thread of an object. This FEP provides a more specific property, which frees up the \"intentionally vague\" context property for other uses. It also avoids the confusing clash with the @context property of JSON-LD.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/7888/","title":"Index","text":"hide: - navigation description: \"ActivityStreams Vocabulary defines the context property, but it is \"intentionally vague\". Unfortunately, this makes the definition so vague as to be practically useless. This FEP aims to provide more guidance on possible uses of the context property, as well as formalizing some best practices.\"
"},{"location":"fep/7888/#fep-7888-demystifying-the-context-property","title":"FEP-7888: Demystifying the context property","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT 2023-03-14 #68 Discussions codeberg"},{"location":"fep/7888/#summary","title":"Summary","text":"ActivityStreams Vocabulary defines the context property, but it is \"intentionally vague\". Unfortunately, this makes the definition so vague as to be practically useless. This FEP aims to provide more guidance on possible uses of the context property, as well as formalizing some best practices.
(This section is non-normative.)
See \"Appendix A: Rationale\" for fuller analysis of the definition, as well as use cases that can be mapped onto context.
In short:
context, audience and tag can be used for similar purposes.context roughly corresponds to the label \"was created in relation to\". Compare to audience, which roughly corresponds to the label \"is considered relevant to\". Compare also to tag, which roughly corresponds to the label \"is associated with a topic of\".context embodies purpose. Things grouped by the same context \"belong together\" in a way that can't be said about things grouped by the same tag.context exist within that context, and are meant to be seen and interpreted in context of something else. In most cases, you do not want to view the object on its own; it should be viewed together with other objects, contextually. Deleting a context might reasonably delete or garbage-collect objects within that context.context to represent a \"thread\", \"topic\", \"conversation\", \"room\", \"channel\", \"forum\", \"wall\", \"guild\", \"space\", \"project\", or so on.The requirements below can be summarized like so:
context for signaling which objects belong together purposefully, i.e. objects that are meant to be viewed or processed together. Ideally, make the context resolve to something useful. Depending on which properties the resolved context has, various use cases can make use of those properties.attributedTo some owner, others can keep that owner in the loop when interacting with their context. Similar considerations apply to followers and audience.context to group related objects by the context's id. The graph source for those objects is up to you, but in the case where your source is an SQL database, it probably makes sense to have the context.id be the value of a column which is indexed so you can efficiently use a WHERE clause in your query.context might exist within that same context, or they might declare their own context, or they might not declare a context. If you're declaring someone else's context, then you might want to keep them in the loop, in the same way that you'd keep someone in the loop if you replied to their object or tagged their object. This would be signaled via context.attributedTo similarly to inReplyTo.attributedTo or tag[*].attributedTo.context","text":"When generating an object with a context as a publisher:
A context SHOULD have a purpose; consider tag for looser references. Objects sharing a certain context SHOULD be strongly related and intended to be viewed in the same grouping. Deleting the context MAY delete all objects within that context.
A context SHOULD be resolvable. The resolved object or link can describe the context with at least the additional information needed to fully process the activity or object. Examples of generally useful properties include but are not limited to:
attributedTo denotes the authority for that context. (This authority might be a good target for addressing and delivery of related activities.)audience indicates intent for or potential interest by some entities. (These entities might be a good target for addressing and delivery of related activities.)followers signals that the context might be a followable object. (This followers collection might be a good target for addressing and delivery of related activities.)outbox might contain relevant Activities performed by the context, if it is an actor.Ideally, the resolved context SHOULD in some way have an associated Collection which can contain the related items. (The exact semantics of discovering this Collection are out of scope of this FEP.)
context","text":"When encountering an object with a context as a consumer or browser:
context","text":"At minimum, you SHOULD consider the current object alongside other objects referencing the same context (by id) instead of considering the current object independently. By default, the graph source for objects that are being considered for inclusion is arbitrary. This can be some dataset, or it can be some relevant collection's items. For example, you might conssider the outbox and/or inbox of one or more actors, or you might consider a specific property path on the context (if resolvable).
If the context resolves to an object of a certain type, then that type MAY indicate that a certain relation represents a canonical Collection of all objects that the authority considers to be included. (The definition of such types and relations is out of scope of this FEP.)
For authoritative contexts that include such a canonical Collection, you SHOULD NOT assume that an object has been accepted into that collection simply because it declares context. Consumers SHOULD make efforts to verify reverse claims of inclusion. If a client or user-agent is unable to verify this claim, then the client or user-agent SHOULD indicate to users that the object's claim of being included in the authoritative context is unverified. Criteria for establishing proof of inclusion in a collection is out of scope for this FEP, but might include:
context","text":""},{"location":"fep/7888/#choosing-whether-to-participate-in-the-same-context","title":"Choosing whether to participate in the same context","text":"When encountering an object with a context and choosing to author your own object or activity that interacts with this object:
context as-is, if you wish for your object to be included in that same context.context, if you wish for your object to exist in a different context.context entirely, if you wish for your object to exist on its own.Note that context can be present on either the object, the activity, or both. It is also possible for different context references to be placed on each. This depends on how context is used within a given protocol. Protocol considerations for when to use certain contexts are out-of-scope for this FEP. Protocol considerations for how to negotiate participation in someone else's context are also out-of-scope for this FEP.
Per PUB Section 6.1 \"Client Addressing\":
Clients SHOULD look at any objects attached to the new Activity via the object, target, inReplyTo and/or tag fields, retrieve their actor or attributedTo properties, and MAY also retrieve their addressing properties, and add these to the to or cc fields of the new Activity being created. Clients MAY recurse through attached objects, but if doing so, SHOULD set a limit for this recursion. (Note that this does not suggest that the client should \"unpack\" collections of actors being addressed as individual recipients).
Clients MAY give the user the chance to amend this addressing in the UI.
This FEP extends the recommendation to look at object, target, inReplyTo, and/or tag to also include context.
If copying someone else's context, you SHOULD send your activity to the owner(s) of the context(s), defined via context.attributedTo if resolvable. This is similar to how one might address the author of an object that they are responding to via inReplyTo.attributedTo, as a social courtesy. You MAY also want to address context.followers and/or addressing properties like context.audience.
(This section is non-normative.)
"},{"location":"fep/7888/#the-existing-definition","title":"The existing definition","text":"From the current definition in VOCAB: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-context
Identifies the context within which the object exists or an activity was performed.
The notion of \"context\" used is intentionally vague. The intended function is to serve as a means of grouping objects and activities that share a common originating context or purpose. An example could be all activities relating to a common project or event.
Aside from being \"intentionally vague\", the definition is also somewhat circular; it requires knowing what a context is and having some conceptual understanding of the notion of \"context\". However, we are given some guidance towards its \"intended function\", which is to group objects by some common purpose or origin.
"},{"location":"fep/7888/#supporting-statements-from-spec-authors","title":"Supporting statements from spec authors","text":"In a GitHub issue from ActivityStreams 2.0's development cycle, James Snell provides the following example:
{\n \"type\": \"Note\",\n \"content\": \"This is a note\",\n \"scope\": {\n \"type\": \"Organization\",\n \"name\": \"My Employer\"\n },\n \"to\": [\"john@example.com\", \"sally@example.com\"],\n \"context\": {\n \"type\": \"http://example.org/types/Project\",\n \"name\": \"A Project\"\n }\n}\n James Snell then comments that:
scope indicates that the audience for the note is only members of the Organization.to indicates specific people who should be actively notified.context indicates a larger context within which the note exists.James Snell then clarifies (emphasis added):
scope is not access control [...] a consuming implementation may include the note on the activity timeline of anyone associated with the \"My Employer\" organization, but it would only notify two individuals listed by the to property. The context property, on the other hand, has absolutely nothing to do with audience targeting. The above note is essentially saying, \"This is a note that was created in relation to A Project. Make the note available to anyone in the My Employer organization but specifically notify John and Sally\"
Therefore, we can establish that context as a property roughly translates to a label of \"was created in relation to\".
In a separate issue, James Snell provides another explanation:
The context is really intended to allow objects and activities to be logically grouped. For instance, in an enterprise setting, the context may group activities by project while the scope would identify one or more teams for which the activity is considered relevant, while the to/cc fields are used to indicate specific individuals to notify.
scope was later renamed to audience, but the two properties remain closely related and are presented together in AS2-VOCAB Section 5.1.1 \"Audience and Context\":
Activities are rarely isolated events. Often, multiple individual activities will be performed around a similar context or audience. For instance, a collaborators working on a shared project might perform multiple related activities in the process of achieving some goal. Such activities can be logically grouped together using the context property, and scoped to a particular audience using the audience property.
"},{"location":"fep/7888/#purpose-and-intent-or-why-not-use-a-tag","title":"Purpose and intent; or, why not use a tag?","text":"We might similarly use a tag for grouping objects and activities. Several fediverse projects often include a Hashtag (defined as an extension within the ActivityStreams namespace, but not actually adopted or defined formally). This Hashtag signals an intent to be included or discovered through a collection of objects bearing the same Hashtag, uniquely identified by its name. The maintenance of such implicit collections is assumed to be the responsibility of the receiving server, although an href might be provided for convenience, in order to browse the implicit collection of tagged objects as seen from that origin server. (This also makes the Hashtag a sub-type of Link.)
The key property of such a tag is to signal a general, implicit association by reference. We might then consider a context to be an explicit association, but such an explicit association requires an explicit definition.
"},{"location":"fep/7888/#the-different-types-of-context-and-how-they-are-actually-the-same","title":"The different types of context, and how they are actually the same","text":"Various dictionaries define context generally as something that helps you understand the situation. Following from this, the context should be something that helps you process the activity or object. Ignoring the context may lead to misunderstanding the activity or object; the object or activity exists within that context, and should be understood in context of that context.
Specific contexts can be thought of in several applications:
We might continue to articulate further types of contexts, but the general pattern that emerges is that a context exists to form a purposeful grouping, regardless of the specific purpose. For example, if we had the notion of a conversation, then we might reasonably say that someone owns this conversation and can apply their authority to it. Looking at some object or activity within this context is generally not recommended on its own; it is better to view the entire conversation or some page of it rather than viewing a singular object.
"},{"location":"fep/7888/#sample-workflows-and-use-cases-involving-context","title":"Sample workflows and use-cases involving context","text":"The context may be presented using the following abstractions:
Contexts may be associated with other contexts:
It is also possible to not have a context. Such objects exist only in the general context of their author (via attributedTo) or other implicit contexts, and are otherwise self-sufficient.
Considerations on when to use context include:
(This section is non-normative.)
"},{"location":"fep/7888/#example-1-a-minimal-example-for-grouping-objects-by-context","title":"Example 1: A minimal example for grouping objects by context","text":"This example demonstrates how objects sharing the same context can be logically grouped together.
You encounter the following object:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-object\",\n \"context\": \"https://domain.example/some-context\",\n \"summary\": \"<some-object> exists in <some-context>.\"\n}\n You wish to participate in the same context, so you dereference the context in order to learn more about it:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-context\",\n \"attributedTo\": \"https://domain.example/context-owner\",\n \"summary\": \"<some-context> is owned by <context-owner>.\"\n}\n You create an object, while copying that context onto your object:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/your-object\",\n \"context\": \"https://domain.example/some-context\",\n \"summary\": \"<your-object> exists in <some-context> as well.\"\n}\n Distribution occurs somehow; you may want to notify the <context-owner> or seek their acknowledgement of your object, but these things are out-of-scope of this example. A graph source or dataset containing these two objects may be queried for objects sharing the same context:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/results-for-your-query\",\n \"type\": \"Collection\",\n \"summary\": \"The <results-for-your-query> show that 2 items have a context of <some-context>. They are <some-object> and <your-object>.\",\n \"totalItems\": 2,\n \"items\": [\n \"https://domain.example/some-object\",\n \"https://domain.example/your-object\"\n ]\n}\n"},{"location":"fep/7888/#example-2-choosing-not-to-participate-in-the-same-context","title":"Example 2: Choosing not to participate in the same context","text":"This example demonstrates how objects can have different contexts, indicating that they were created for different purposes. Though they may be grouped by other criteria, they do not share a primary reason for existing.
You encounter the following object:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-object\",\n \"context\": \"https://domain.example/some-context\",\n \"summary\": \"<some-object> exists in <some-context>.\"\n}\n You want to establish your own context, separately from the current object's context:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/a-different-context\",\n \"attributedTo\": \"https://domain.example/you\",\n \"summary\": \"<a-different-context> is owned by <you>.\"\n}\n You may declare that your object is in some way a response to the object that you encountered, but because the contexts are the same, they do not share a primary grouping:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/your-object\",\n \"inReplyTo\": {\n \"id\": \"https://domain.example/some-object\",\n \"context\": \"https://domain.example/some-context\",\n \"summary\": \"<some-object> exists in <some-context>.\"\n },\n \"context\": \"https://domain.example/a-different-context\",\n \"summary\": \"<your-object> is a response to <some-object>, but <some-object> exists in <some-context> while <your-object> exists in <a-different-context>.\"\n}\n Querying replies for the original object might surface your object, but querying the context for the original object will not surface your object.
Later, <some-context> is deleted. In some cases, <some-object> might be garbage-collected, since it has lost its reason or purpose for existing; at best, it is considered orphaned. However, <your-object> continues to exist because it was created in <a-different-context> which still exists.
This example demonstrates how one might deal with objects that have multiple contexts.
You encounter an object with multiple contexts:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-object\",\n \"context\": [\"https://domain.example/some-context\", \"https://domain.example/some-other-context\"],\n \"summary\": \"<some-object> exists in <some-context> and <some-other-context>.\"\n}\n You dereference the two contexts:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-context\",\n \"attributedTo\": \"https://domain.example/context-owner\",\n \"summary\": \"<some-context> is owned by <context-owner>.\"\n}\n {\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-other-context\",\n \"attributedTo\": \"https://domain.example/other-context-owner\",\n \"type\": \"Object\",\n \"summary\": \"<some-other-context> is owned by <other-context-owner>.\"\n}\n As a third-party observer, you can choose to browse either context.
As a third-party interactor, you can choose to declare an object in either context, both contexts, a different context, or no context.
The protocol considerations for which contexts are considered valid or acceptable are out-of-scope of this FEP, but dereferencing the contexts can provide more information that can help you make this choice. Perhaps you expect a certain type to be declared, or perhaps you require an owner, or perhaps some other criteria is enforced.
"},{"location":"fep/7888/#example-4-publishing-consuming-and-interacting-with-authoritative-contexts-that-have-canonical-collections","title":"Example 4: Publishing, consuming, and interacting with authoritative contexts that have canonical collections","text":"This example demonstrates how one might expose all objects acknowledged by a context owner to exist within the context.
You encounter the following object:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-object\",\n \"context\": \"https://domain.example/some-context\",\n \"summary\": \"<some-object> exists in <some-context>.\"\n}\n You wish to browse that context, so you dereference the context:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://domain.example/some-context\",\n \"attributedTo\": \"https://domain.example/context-owner\",\n \"type\": \"https://w3id.org/fep/xxxx/Conversation\",\n \"https://w3id.org/fep/xxxx/posts\": {\n \"id\": \"https://domain.example/some-context/posts\",\n \"type\": \"OrderedCollection\",\n \"items\": [\n \"https://domain.example/some-object\",\n // ...\n ]\n }\n \"summary\": \"<some-context> is owned by <context-owner>. It is a <Conversation> and it has a canonical collection of <posts>, which is <some-context/posts>.\"\n}\n As a consumer, you can browse or backfill the conversation by loading the context's canonical collection. In the above representation of <some-context>, the use of the hypothetical https://w3id.org/fep/xxxx/Conversation type would indicate that the associated canonical collection is exposed via the hypothetical https://w3id.org/fep/xxxx/posts property.
(This section is non-normative.)
Because PUB does not define the use of context as a property or the notion of a canonical collection, it is up to ActivityPub Clients to manage contexts and their canonical collections for themselves. The following algorithm may be used to create an object within a context that has a canonical collection:
Create the canonical Collection that will be associated with the context. Save the generated Collection id to be used in the next step.context. If the Object has a canonical Collection associated with it, then specify the appropriate property relation using the id from the previous step. Save the generated context id to be used in the next step.context as the id from the previous step. Set an appropriate audience or use to/cc to deliver the Create activity as-is. Save the generated object id to be used in the next step.ids obtained from the responses for steps 1 and 3. You may wish to deliver this Add activity via to/cc/audience targeting your intended recipients, especially if you did not deliver the Create Object from step 3.CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/7952/","title":"FEP-7952: Roadmap For Actor and Object Portability","text":"Authors Status Date received Tracking issue Discussions Repository Dmitri Zagidulin @dmitri@social.coop, bumblefudge bumblefudge@learningproof.xyzDRAFT 2024-06-20 #435 Discussions codeberg"},{"location":"fep/7952/#summary","title":"Summary","text":"Portability: A set of design choices, data models, and protocols, that enable an end-user to automatically migrate from one service provider to another with the least amount of data loss and service disruption, including loss of or disruption to the user's social graph (Followers and Following collections, etc).
We focus on automated migration because the option of fully manual migration always exists, such as re-typing all of one's messages and content, manually re-adding everyone to one's following collection, manually contacting everyone on one's former followers list and asking them to re-follow, and so on. However, that's an extreme failure state in usability terms; we want to avoid that.
This FEP targets two main categories of migration, covering user stories 1A-1F and 2 + 3 in FEP-73cd: User Migration Stories, respectively. It also draws inspiration from an earlier information document, FEP-cd47: Federation-friendly Addressing and Deduplication Use-Cases.
"},{"location":"fep/7952/#migration-from-a-live-cooperating-server","title":"Migration from a live (cooperating) server","text":"Note that the data handling and formatting recommendations made here will be strictly complementary to the in-progress LOLA proposal, which target the same user stories.
"},{"location":"fep/7952/#migration-from-a-backupexport-produced-by-a-dead-or-non-cooperating-server","title":"Migration from a backup/export produced by a dead (or non-cooperating) server","text":""},{"location":"fep/7952/#current-approaches","title":"Current Approaches","text":"Most current Fediverse implementations tend to follow a pattern of comprehensive service providers (or instances) that bundle the following concerns into a single service running on a web domain:
This tight coupling of concerns constrains the possibilities for migrating from a live server, and the daunting task of automating migrations to other, less-bundled architectures has yet to be attempted. Its strict reliance on interactive HTTP signatures in concern #2 also excludes migration from a dead server altogether, severely degrading user experience when servers go offline.
For example, a typical current Move Actor implementation (such as the one implemented by Mastodon, or specified in FEP-7628) only assists the user with automatically migrating their Followers list (by setting up a bi-directional alsoKnownAs links and sending the Move activity to all of their followers). In addition, some implementations allow the manual export and re-import of the Following lists, mutes, blocks and bookmarks. However, important content like posts, media attachments, reactions, reposts and so on, does not get automatically transferred.
This FEP proposes a roadmap (a set of steps, design concepts, and recommendations) that enables more complete live migrations, as well as enabling migrations from dead server backups.
"},{"location":"fep/7952/#roadmap-for-portability","title":"Roadmap For Portability","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this specification are to be interpreted as described in RFC-2119.
"},{"location":"fep/7952/#1-unbundle-the-services-and-concerns-of-a-typical-instance","title":"1. Unbundle the services and concerns of a typical instance","text":"Separating the object storage provider from the Actor profile hosting provider enables the implementation of Object IDs that can remain unchanged through migrations and export/import cycles. Specifically, we're looking for Object IDs that remain unchanged even after migrating the objects to be hosted at a different storage provider.
There are several methods of achieving these types of portable IDs, including:
ap:// URI scheme.https URLs that are redirected to their current location), specified in FEP-e3e9: Actor-Relative URLsAs long as a user's identity provider's web domain remains constant, they can migrate to different inbox/outbox and storage providers, without breaking portable links. That is, users will be able to switch hosting and service providers to different domains, without breaking signatures, replies and threading, likes, following and followers, or anything else.
This means that:
https://alice-personal-site.example or https://example.com/users/alice) can now also be blocked/moderated or accrue reputational weightinghttps://storage-provider.example) might also be worth tracking separately for some moderation or spam-/sybil-protection use-cases.Exporting an account based on this model segments neatly and brings with it verifiability, if each object was separately signed by a key published in the still-live Actor object. Importing from such a file is trivial to implement compared to today's account migrations that need to be aware of the data structures and addressing schemes of the previous server.
"},{"location":"fep/7952/#4-enable-verifiable-actor-url-migration","title":"4. Enable verifiable Actor URL migration","text":"If the user wants to move their Actor to a different domain, or switch identity hosting services, a future FEP could define this in a simple, Verifiable Credential-style data model containing attestations and proofs that the new actor is equivalent to the old, controlled by the same subject, etc. This would generalize and standardize one currently thorny requirement of interoperable migration, that of authenticating migration requests.
Note: one possible extension of this authentication artefact would be to incorporate \"pre-rotation\" style commitments of the sort used in distributed key management.
"},{"location":"fep/7952/#end-result-stable-migration-aware-distributed-data","title":"End Result: Stable, migration-aware distributed data","text":"Taken together, these items allow for painless automated migration between service providers, without broken links or effort duplication, and enables restoration from backup and thus migration from dead servers.
"},{"location":"fep/7952/#examples","title":"Examples","text":""},{"location":"fep/7952/#example-1-standalone-activitypub-actor-profile","title":"Example 1: Standalone ActivityPub Actor Profile","text":"Hosted at https://alice-personal-site.example/actor
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/did/v1\",\n \"https://w3id.org/security/multikey/v1\"\n ],\n \"id\": \"https://alice-personal-site.example/actor\",\n \"type\": \"Person\",\n \"name\": \"Alice\",\n \"preferredUsername\": \"alice\",\n \"inbox\": \"https://service-provider.example/users/alice/inbox\",\n \"outbox\": \"https://service-provider.example/users/alice/outbox\",\n \"assertionMethod\": [\n {\n \"id\": \"https://alice-personal-site.example/actor#ed25519-key\",\n \"type\": \"Multikey\",\n \"controller\": \"https://alice-personal-site.example/actor\",\n \"publicKeyMultibase\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n }\n ],\n \"service\": [{\n \"id\": \"https://alice-personal-site.example/actor#storage\",\n \"serviceEndpoint\": \"https://storage-provider.example/users/1234\"\n }]\n}\n Things to note:
https://alice-personal-site.example/actor, but the Inbox and Outbox services are hosted separately, at https://service-provider.example/users/alice/inboxassertionMethod section, as defined in FEP-521a.service section, which sets up the ability to use Actor-Relative URLs for mapping (and thus routing) all requests to their current locations at service providers (see FEP-e939).FEP-e939: Actor-Relative URLs
Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/7d8c/","title":"FEP-7d8c: Documentation: Automation of FEP","text":"Authors Status Date received Tracking issue Discussions Repository Helge @helge@mymath.rocksDRAFT 2025-01-20 #470 Discussions codeberg"},{"location":"fep/7d8c/#summary","title":"Summary","text":"This FEP discusses scripts and woodpecker configuration used to automate parts of the FEP process. The FEP process is described in FEP-a4ed.
As FEP-a4ed, this is a living document, and should be updated as the FEP process evolves.
"},{"location":"fep/7d8c/#vision","title":"Vision","text":"Automation has the goal of reducing the burden on facilitators by:
Once one has simplified this job, one can seek broader adoption of the FEP process..
"},{"location":"fep/7d8c/#todos","title":"Todos","text":"This is a living document. So let's have a todo list
The first two items have been on my personal todo list for a long time, and might stay there ... so feel free to pick them up.
"},{"location":"fep/7d8c/#automation","title":"Automation","text":"Automation for the FEP repository uses the codeberg woodpecker. There are two jobs:
README.md file and creates the tracking issueAutomation code is written in python and is contained in the script folder.
"},{"location":"fep/7d8c/#scripts-and-tests","title":"Scripts and tests","text":"FEP uses python scripts to manage some aspects of it. These should be runnable without installing packages. In order to ensure these scripts stay working, include them in .woodpecker/test.yml
"},{"location":"fep/7d8c/#configuration-variables","title":"Configuration variables","text":"To be able to do these configuration tasks, you need to be in the admin group. See the first discussion here.
The configuration .woodpecker/readme.yml uses two secrets:
Furthermore, the environment variable CI_REPO_CLONE_SSH_URL is used see woodpecker documentation.
We will now discuss how to obtain these secrets.
"},{"location":"fep/7d8c/#the-api-token","title":"The api token","text":"API tokens can be generated on codeberg under user settings -> applications -> select permissions -> issue: Read and Write and giving it a name. They are hexstrings with 40 characters (if I counted correctly), e.g.
33104dd6847e87ef3d6925effdfc852349233034\n"},{"location":"fep/7d8c/#deploy-key","title":"Deploy key","text":"As already mentioned the deploy key is an ssh key. One can generate these on linux via
ssh-keygen -t ed25519 -f deploy_key\n Do not specify a passphrase. Examples:
$ cat deploy_key\n-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\nQyNTUxOQAAACCQ5XqPCdvHPHsukhPS+RMDpMnOCCLW0csGGoFgijQRiwAAAJDpM7fx6TO3\n8QAAAAtzc2gtZWQyNTUxOQAAACCQ5XqPCdvHPHsukhPS+RMDpMnOCCLW0csGGoFgijQRiw\nAAAEB2ofBDBwUcZs5FHsaFMvyO95Qrvn8+PO3BchraucMRwJDleo8J28c8ey6SE9L5EwOk\nyc4IItbRywYagWCKNBGLAAAADWhlbGdlQHJhaW5ib3c=\n-----END OPENSSH PRIVATE KEY-----\n$ cat deploy_key.pub\nssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJDleo8J28c8ey6SE9L5EwOkyc4IItbRywYagWCKNBGL helge@rainbow\n The public key deploy_key.pub should be added to the codeberg repo under settings -> deploy keys -> add deploy key and checking the enable write access checkbox.
See here.
"},{"location":"fep/7d8c/#adding-the-secrets-to-woodpecker","title":"Adding the secrets to woodpecker","text":"One can add these secrets on woodpecker, then selecting the settings wheel -> secrets -> add secret. The secrets only need to available for the push action (corresponding to a merged pull request).
The deploy_key needs to be end with a new line when pasting into the woodpecker ui.
"},{"location":"fep/7d8c/#development","title":"Development","text":"The tools are located in the scripts folder, with the scripts/fep_tools folder containing the python package fep_tools. Most of this tooling only uses python, e.g. scripts/new_proposal.py and scripts/create_readme.md, so they can be run from any environment.
To run the tests, one needs pytest. We note that the tests run from the base folder. The following commands demonstrate how to setup a virtual environment, install the dependencies, and run the tests.
python -mvenv .venv\npip install ./scripts\npytest\n One can leave the venv, by running deactivate.
To build the website, first install the dependencies
python -mvenv .venv\npip install ./scripts\n./scripts/create_site_pages.py\n Then run mkdocs via
mkdocs serve --config-file scripts/mkdocs.yml\n References:
See website.yml.
"},{"location":"fep/7d8c/#changes","title":"Changes","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/82f6/","title":"FEP-82f6: Actor statuses","text":"Authors Status Date received Tracking issue Discussions Repository Gregory Klyushnikov activitypub@grishka.meDRAFT 2025-05-12 #600 Discussions codeberg"},{"location":"fep/82f6/#summary","title":"Summary","text":"This proposal describes an ActivityPub extension to allow actors to publish a short status text, with optional expiration, link attachment, and history.
Some centralized communication services provide their users with the ability to set a status on their account, which is usually displayed on their profile and sometimes next to their name in other places in the UI. These are distinct from regular posts because they can not be interacted with in any way whatsoever, can't contain media attachments, and usually have a short character limit on the order of several hundred characters at most. Statuses are always visible to anyone who can see the actor itself.
"},{"location":"fep/82f6/#requirements","title":"Requirements","text":"The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].
"},{"location":"fep/82f6/#representation-of-statuses","title":"Representation of statuses","text":"A status object is of type ActorStatus, which extends the ActivityPub Object. It has the following fields:
attributedTo: the ID of the actor whose status this is. REQUIRED.content: the plain text content of the status. REQUIRED.published: the timestamp when this status update was created. REQUIRED.id: a unique identifier for this status update. REQUIRED.endTime: a timestamp when this status expires. OPTIONAL. If present, an implementation MUST check this field and, if it is in the past, MUST NOT display this status on the actor's profile and in any other places where statuses are displayed.attachment: an object that contains any extra information about what the actor is currently doing, e.g. a song they're listening to or a video game they're playing. OPTIONAL. If present, content MUST contain a fallback human-readable plain text representation of this object. For example, for a song, it may simply be \"Artist \u2014 Title\".The following two OPTIONAL fields are added to actors:
status: the last, or current, status update of this actor. MUST NOT be present if the last status has expired. MUST contain an inlined ActorStatus.statusHistory: a collection of all past status updates of this actor. If present, implementations MAY provide a UI to view this actor's status history. If absent, implementations SHOULD NOT store this actor's past status updates and MUST NOT expose them in the UI.A status update is created by sending a Create{ActorStatus} activity to followers. Upon receiving this activity, an implementation updates the actor's latest status, and, if it has the statusHistory collection, adds it to the history. After sending this activity, the status field of the actor object MUST be updated with this new status. An Update{Actor} MUST NOT be sent because the Create already implicitly updates that field in the copies of this actor stored on remote servers.
If the actor does not have a statusHistory, the previous status is considered no longer existing, as if it was Deleted.
The latest status is cleared by sending a Remove{ActorStatus} activity to followers. Upon receiving this activity, if the ID of the object matches the actor's current status, an implementation removes the actor's current status. If the actor has the statusHistory collection, it keeps it there. Same considerations for status apply here.
A status is completely deleted by sending a Delete{ActorStatus} activity to followers. If the actor has the statusHistory collection, the status is removed from there as well as being cleared from the profile. Otherwise, this is activity is identical to Remove.
Statuses can not be interacted with. Therefore, implementations SHOULD NOT send any activities that refer to statuses as their object attributed to actors other than the actor that owns this status. Implementations MUST either ignore any such activities by returning a 2xx response, or reject them by returning a 4xx.
Once published, a status object can not be Updated.
The JSON-LD context for this proposal is as follows:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sm\": \"http://smithereen.software/ns#\",\n \"ActorStatus\": \"sm:ActorStatus\",\n \"status\": {\n \"@type\": \"@id\",\n \"@id\": \"sm:status\"\n },\n \"statusHistory\": {\n \"@type\": \"@id\",\n \"@id\": \"sm:statusHistory\"\n }\n }\n ]\n}\n"},{"location":"fep/82f6/#character-limit","title":"Character limit","text":"Implementations MUST support statuses of up to 100 characters or emoji. Implementations MAY allow statuses that are longer, but they MAY be truncated on the receiving side depending on display and storage constraints. It is RECOMMENDED to apply a 100-character limit to the text field where the user enters their status, but be more permissive when processing status updates from other servers.
"},{"location":"fep/82f6/#ui-examples","title":"UI examples","text":"This section is non-normative.
This section shows examples of how implementations might display status updates to users.
Simple UI without history, similar to Discord or GitHub:
Updating one's status, with the ability to set it to expire:
UI with history, similar to early Facebook:
"},{"location":"fep/82f6/#example-status-object","title":"Example status object","text":"{\n \"type\": \"ActorStatus\",\n \"id\": \"https://example.social/users/1/statuses/1747286633\",\n \"attributedTo\": \"https://example.social/users/1\",\n \"content\": \"is desperately trying to bring the old internet back\",\n \"published\": \"2025-05-15T05:23:53.539Z\",\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sm\": \"http://smithereen.software/ns#\",\n \"ActorStatus\": \"sm:ActorStatus\"\n }\n ]\n}\n"},{"location":"fep/82f6/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/844e/","title":"FEP-844e: Capability discovery","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2025-06-14 #624 Discussions codeberg"},{"location":"fep/844e/#summary","title":"Summary","text":"Capability discovery for ActivityPub applications.
This document is based on the idea described in FEP-aaa3: Listing Implemented Specifications on the Application Actor.
"},{"location":"fep/844e/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/844e/#application-object","title":"Application object","text":"An application can advertise its capabilities using the implements property on an Application object.
This object might not be an actor. The value of implements property MUST be an array of Link objects, each containing the following properties:
href (REQUIRED): the unique identifier of the capability. The value MUST be an URI.name (RECOMMENDED): the short description of the capability.Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/844e\"\n ],\n \"type\": \"Application\",\n \"id\": \"https://social.example/server\",\n \"implements\": [\n {\n \"href\": \"https://datatracker.ietf.org/doc/html/rfc9421\",\n \"name\": \"RFC-9421: HTTP Message Signatures\"\n }\n ]\n}\n"},{"location":"fep/844e/#discovery-through-an-actor","title":"Discovery through an actor","text":"An Application object can be linked to an actor using the generator property. The value of this property SHOULD be a partial object containing the implements property. That object MAY be anonymous (without an identifier).
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/844e\"\n ],\n \"id\": \"https://social.example/actors/1\",\n \"type\": \"Person\",\n \"inbox\": \"https://social.example/actors/1/inbox\",\n \"outbox\": \"https://social.example/actors/1/outbox\",\n \"generator\": {\n \"type\": \"Application\",\n \"implements\": [\n {\n \"href\": \"https://datatracker.ietf.org/doc/html/rfc9421\",\n \"name\": \"RFC-9421: HTTP Message Signatures\"\n }\n ]\n }\n}\n"},{"location":"fep/844e/#discovery-through-a-well-known-endpoint","title":"Discovery through a well-known endpoint","text":"The mechanism of discovering an Application object using WebFinger is described in FEP-d556.
Capability discovery through NodeInfo endpoint is discouraged.
"},{"location":"fep/844e/#intended-use","title":"Intended use","text":"The reliance on the mechanism described in this document might increase implementation complexity and hinder interoperability.
Implementers MUST NOT use it in cases where capabilities can be inferred from properties and types of objects.
"},{"location":"fep/844e/#registry-of-software-capabilities","title":"Registry of software capabilities","text":"(This section is non-normative.)
Name Identifier RFC-9421: HTTP Message Signatures https://datatracker.ietf.org/doc/html/rfc9421 RFC-9421 signatures using the Ed25519 algorithm https://datatracker.ietf.org/doc/html/rfc9421#name-eddsa-using-curve-edwards25"},{"location":"fep/844e/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/888d/","title":"FEP-888d: Using https://w3id.org/fep as a base for FEP-specific namespaces","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT 2023-04-10 #83 Discussions codeberg"},{"location":"fep/888d/#summary","title":"Summary","text":"It is considered best practice in the linked-data ecosystem to have IRIs be HTTPS URIs that resolve to a definition of the term being used, and it is desirable to define such terms in a JSON-LD context file that is referenced by its IRI rather than having the full @context object embedded in every single document. ActivityStreams 2.0 and ActivityPub do this with the normative context and namespace provided at https://www.w3.org/ns/activitystreams, but this namespace is not generally open to extensions or to experimental terms. This FEP therefore proposes using https://w3id.org/fep as a base IRI for the FEP process, allowing sub-namespaces for each FEP.
(This section is non-normative.)
The core idea behind this FEP is attributed to helge on SocialHub 1. Specifically, the proposal to register fep at the W3ID service is adopted wholesale, with alterations made to the specifics of implementing the redirect mappings using .htaccess rules. These alterations are intended to allow easier sub-namespace allocation for each FEP.
The key words \"MUST\", \"SHOULD\", \"MAY\" are to be interpreted as described in RFC-2119.
"},{"location":"fep/888d/#introduction","title":"Introduction","text":"(This section is non-normative.)
The Resource Description Framework (RDF), of which JSON-LD is a serialization, uses URIs to identify nodes on a graph, define properties of those nodes, and create relationships between those nodes. Each statement in RDF represents a fact that is constructed by linking a subject to an object with a predicate; for example, in the statement \"Alice knows Bob\", the subject Alice is related to the object Bob by the predicate knows. To avoid ambiguity, we can specify a URI for what it means to \"know\" someone. Such a URI represents a named property or named predicate, and it exists within a namespace, often associated with some ontology or vocabulary. ActivityStreams 2.0 provides and defines the Activity Vocabulary at https://www.w3.org/ns/activitystreams, and terms defined within may use either the base IRI https://www.w3.org/ns/activitystreams# or the compact IRI as:.
For example, we can consider the definition of \"Public\" addressing within ActivityPub, represented by the Public magic collection. When the normative ActivityStreams 2.0 context is applied, the IRI for this collection may be equivalently expressed as Public, as:Public, or https://www.w3.org/ns/activitystreams#Public.
Historically, extension terms within early ActivityPub implementations assumed that those extension terms would be readily adopted within the ActivityStreams namespace, but this did not happen. Currently, terms such as Hashtag, manuallyApprovesFollowers, movedTo, and sensitive are manually defined with compact IRIs using the as: prefix, in effect making it so that any implementation that wishes to understand these properties and types must manually define those terms as well, in the exact same way.
Later extension terms were defined within vendor-specific namespaces such as http://joinmastodon.org/ns or https://joinpeertube.org/ns. With the increased adoption of ActivityPub by software projects and the diverse needs of such projects, each project will often define its own vendor-specific namespace to contain its own terms. This has led to a multitude of namespaces and extension terms, which must be cherry-picked as needed by JSON-LD consumers wishing to maintain compatibility. Additionally, some of these terms are defined incorrectly within @context, leading to the necessity of compatibility hacks on a per-project basis. Even terms defined correctly may overlap with other terms, and proposed new terms must be parented within a vendor namespace, creating the potential for conflict on which vendor should adopt which term.
The aim of this FEP is to provide a vendor-independent namespace under which extension terms can be defined pursuant to the FEP process and the above problems can be reduced.
"},{"location":"fep/888d/#prior-art","title":"Prior art","text":"(This section is non-normative.)
Within the XMPP ecosystem, the core XMPP specification is defined within an RFC, and further functionality is afforded by the \"eXtensible\" nature of XMPP. The XMPP Standards Foundation (XSF) maintains the process for stewarding new extensions via XMPP Extension Protocols (XEPs). XML namespacing for such extensions is provided by urn:xmpp:, as the XML ecosystem generally prefers using URNs rather than using HTTPS URIs. Such URNs are fully location-independent and not vulnerable to DNS expiry, lapsing, or insolvency. Instead, they are assigned within the authority of the XSF. The XSF maintains an XMPP Registrar and allows XEPs to request and define sub-namespaces beneath urn:xmpp:. In exchange, these URNs are not generally dereferencable without a resolver that looks up the URN within the XEP database.
Within the RDF and linked-data ecosystems, there is a strong preference for HTTP or HTTPS URIs, as these can usually be dereferenced via the HTTP protocol for additional information about the subject of the URI. In cases where the URI does not resolve, the URI serves as an identifier not much different than a URN, but with its authority derived from DNS domain rather than from some organizational authority. The reliance on DNS domain creates an issue where the primary domain associated with a group or organization might change. If a previously-used domain is reassigned to a different party, then the new party can mint URIs that accidentally or intentionally conflict with previously-assigned URIs.
To mitigate the DNS authority reassignment issue, trusted intermediary services can maintain a \"persistent URL\" (PURL) service, which allows assigning identifiers on the intermediary domain that will redirect to some other URI. This layer of indirection allows changing the location of the resource by simply changing the redirect's target. W3ID is one such service, operated by the W3C Permanent Identifier Community Group and available at https://w3id.org. At the time of writing this FEP, top-level directory names can be claimed by individuals who submit pull requests to the w3id.org repository on GitHub, and .htaccess files allow redirection based on rewrite rules that transform incoming requests to some other target.
"},{"location":"fep/888d/#specification","title":"Specification","text":""},{"location":"fep/888d/#design-goals","title":"Design goals","text":"Broad design goals for the redirect mapping include:
Accept: application/ld+json HTTP header, and SHOULD otherwise return human-friendly proposal documents or term definitions by default.At minimum, the following redirects SHOULD resolve as follows:
https://w3id.org/fepAccept: * => the FEP repository or current home pagehttps://w3id.org/fep/(:id)Accept: application/ld+json => a specific FEP's context documentAccept: * => a specific FEP's proposal documentAdditionally, the following MAY resolve:
https://w3id.org/fep/(:id)/(:term)Accept: application/ld+json => a specific FEP's specific term definition in JSON-LD ontology/schemaAccept: application/rdf+xml => a specific FEP's specific term definition in RDF/XML ontology/schemaAccept: text/turtle => a specific FEP's specific term definition in Turtle ontology/schemaAccept: * => a specific FEP's specific term's folderAt the time of writing this FEP, the Codeberg repository at https://codeberg.org/fediverse/fep is used to host FEP-related files, and can similarly be used to host context documents.
(This section is non-normative.)
An example .htaccess file is co-located with this FEP, and reproduced below for convenience:
RewriteEngine on\n\n\n# catch root request\nRewriteRule ^\\/?$ https://codeberg.org/fediverse/fep [R=302,L]\n\n\n\n# Catch FEP documents\n\n## By content negotiation\n\n### JSON-LD\nRewriteCond %{HTTP_ACCEPT} application/ld\\+json\nRewriteRule ^([A-Za-z0-9]+)\\/?$ https://raw.codeberg.page/fediverse/fep/fep/$1/fep-$1.jsonld [R=302,L]\n\n### RDF+XML\nRewriteCond %{HTTP_ACCEPT} application/rdf\\+xml\nRewriteRule ^([A-Za-z0-9]+)\\/?$ https://fediverse.codeberg.page/fep/fep/$1/fep-$1.rdf [R=302,L]\n\n### Turtle\nRewriteCond %{HTTP_ACCEPT} text/turtle\nRewriteRule ^([A-Za-z0-9]+)\\/?$ https://fediverse.codeberg.page/fep/fep/$1/fep-$1.ttl [R=302,L]\n\n## By URL hacking\nRewriteRule ^([A-Za-z0-9]+).jsonld$ https://raw.codeberg.page/fediverse/fep/fep/$1/fep-$1.jsonld [R=302,L]\nRewriteRule ^([A-Za-z0-9]+).rdf$ https://raw.codeberg.page/fediverse/fep/fep/$1/fep-$1.rdf [R=302,L]\nRewriteRule ^([A-Za-z0-9]+).ttl$ https://raw.codeberg.page/fediverse/fep/fep/$1/fep-$1.ttl [R=302,L]\n\n## By default, take you to the FEP document\nRewriteRule ^([A-Za-z0-9]+)\\/?$ https://codeberg.org/fediverse/fep/src/branch/main/fep/$1/fep-$1.md [R=302,L]\n\n\n\n# Catch term definitions/schemas/ontologies\n\n## By content negotiation\n\n### JSON-LD\nRewriteCond %{HTTP_ACCEPT} application/ld\\+json\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?)\\/?$ https://raw.codeberg.page/fediverse/fep/fep/$1/$2/$2.jsonld [R=302,L]\n\n### RDF+XML\nRewriteCond %{HTTP_ACCEPT} application/rdf\\+xml\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?)\\/?$ https://fediverse.codeberg.page/fep/fep/$1/$2/$2.rdf [R=302,L]\n\n### Turtle\nRewriteCond %{HTTP_ACCEPT} text/turtle\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?)\\/?$ https://fediverse.codeberg.page/fep/fep/$1/$2/$2.ttl [R=302,L]\n\n### test html\nRewriteCond %{HTTP_ACCEPT} ^text/html$\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?)\\/?$ https://fediverse.codeberg.page/fep/fep/$1/$2/$2.html [R=302,L]\n\n## By URL hacking\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?).jsonld$ https://raw.codeberg.page/fediverse/fep/fep/$1/$2/$2.jsonld [R=302,L]\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?).rdf$ https://fediverse.codeberg.page/fep/fep/$1/$2/$2.rdf [R=302,L]\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?).ttl$ https://fediverse.codeberg.page/fep/fep/$1/$2/$2.ttl [R=302,L]\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?).html$ https://fediverse.codeberg.page/fep/fep/$1/$2/$2.html [R=302,L]\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?).md$ https://fediverse.codeberg.page/fep/fep/$1/$2/README.md [R=302,L]\n\n## By default, just take you to the term's folder\nRewriteRule ^([A-Za-z0-9]+)\\/(.*?)\\/?$ https://codeberg.org/fediverse/fep/src/branch/main/fep/$1/$2 [R=302,L]\n\n\n\n# a generic catch-all rule\nRewriteRule ^(.*)\\/?$ https://codeberg.org/fediverse/fep/raw/branch/main/fep/$1 [R=302,L]\n"},{"location":"fep/888d/#defining-terms-associated-with-an-fep","title":"Defining terms associated with an FEP","text":"FEPs that wish to define extension terms within the w3id.org/fep namespace MUST provide a JSON-LD document co-located within their FEP folder with a filename of the form fep-xxxx.jsonld, where xxxx is the FEP's slug. This document MUST include at least a @context key, whose value is a JSON object containing term definitions. Simple term definitions map a term key to an IRI value. Expanded term definitions contain @id for each term, with @type of @id if the term links to another node on the graph. Refer to LD-TERM-DFN for additional guidance on defining terms within JSON-LD.
The document MAY include additional metadata outside of the @context, such as schema or ontology definitions. If this is done, then you SHOULD NOT include any prefixes in your @context that are only used within the graph, as these can pollute the usage as a context document.
Once the FEP is marked FINAL, the context document MAY be cached forever if referenced. FEPs that define extension terms MAY instead define extension terms within a vendor-specific namespace, but generally this SHOULD NOT be done.
(This section is non-normative.)
For example, say we wanted to define the following terms within the current FEP-888d:
SomeType is a term for some typeexampleA is a term for a property with some literal value (string, boolean, number)exampleB is a term for a property that links to another node on the graph (for example, another object)exampleC is a term for a property that is an ordered list of literal values that are specifically non-negative integersThe context document might look like this, at minimum:
{\n \"@context\": {\n \"SomeType\": \"https://w3id.org/fep/888d/SomeType\",\n \"exampleA\": \"https://w3id.org/fep/888d/exampleA\",\n \"exampleB\": {\n \"@id\": \"https://w3id.org/fep/888d/exampleB\",\n \"@type\": \"@id\"\n },\n \"exampleC\": {\n \"@id\": \"https://w3id.org/fep/888d/exampleC\",\n \"@type\": \"http://www.w3.org/2001/XMLSchema#nonNegativeInteger\",\n \"@container\": \"@list\"\n }\n }\n}\n Refer to LD-TERM-DFN for additional guidance on defining terms within JSON-LD.
A folder within the FEP's directory should be used to provide additional documentation for the term, such as ontology or schema definitions via JSON-LD, RDF/XML, and/or Turtle.
"},{"location":"fep/888d/#example-using-fragment-identifiers","title":"Example using fragment identifiers","text":"(This section is non-normative.)
Depending on convenience or preference, the context document might instead look like this:
{\n \"@context\": {\n \"SomeType\": \"https://w3id.org/fep/888d#SomeType\",\n \"exampleA\": \"https://w3id.org/fep/888d#exampleA\",\n \"exampleB\": {\n \"@id\": \"https://w3id.org/fep/888d#exampleB\",\n \"@type\": \"@id\"\n },\n \"exampleC\": {\n \"@id\": \"https://w3id.org/fep/888d#exampleC\",\n \"@type\": \"http://www.w3.org/2001/XMLSchema#nonNegativeInteger\",\n \"@container\": \"@list\"\n }\n }\n}\n Refer to LD-TERM-DFN for additional guidance on defining terms within JSON-LD.
In such a case, the FEP document should include an element with an HTML identifier that exactly matches the term name, so that the IRI fragment resolves properly. In practice, this means one of the following:
summary, acknowledgements, requirements, references, copyright, and so on, including any headers that the FEP author includes for purposes other than explicitly defining the term.### h3 {#custom-identifier} and render <h3 id=\"custom-identifier\">h3</h3>. Markdown specifications such as CommonMark currently do not support custom attributes, but some Markdown processors such as Goldmark support custom attributes on headers (but not on arbitrary elements). See CM-ATTRS for more discussion of this feature.id attributes exactly matching the term name. HTML within Markdown files is generally rendered as-is, although it may be sanitized, stripped, or disallowed for security purposes. In cases where it is allowed, however, it can be an effective way to express term definitions within an FEP document.(This section is non-normative.)
If terms are defined within HTML using RDFa, and this HTML is embedded in the Markdown contents of the FEP document, then these term definitions can be used to programmatically generate machine-readable term definitions, schemas, and ontologies in multiple formats. A Python script is provided within the FEP repository at scripts/make_definitions.py for convenience.
To make use of the script, it is necessary to structure term definitions in a specific way. We define a \"term definition\" to be any HTML element that adheres to the following requirements:
id attribute equal to the shorthand term name.resource attribute equal to the term's full IRI.Generally, this HTML element should be a <section> tag, and it should contain a definition list (<dl></dl>) where each term (<dt></dt>) and definition (<dd></dd>) provide a key-value pair representing a property of that term definition. Examples may be provided within a preformatted code block (<pre><code></code></pre>), and multiple examples may be provided. Example code blocks can have a title attribute on the <pre> tag. It is generally recommended to put any term definitions below a heading called something like \"Terms defined\".
For all term definitions regardless of type, it is recommended to include the following properties in your definition list:
rdfs:label is a natural language label that can replace the shorthand term. For example, a term that is normally expressed with the shorthand hasValue might be labelled has value in plain English.rdfs:comment is a natural language description that gives more detail about what the term means.rdfs:isDefinedBy is a reference pointing to where the term is defined. Usually this is the current FEP, but meta-FEPs that bundle other FEPs may use this property to refer to the original FEP that defined a term.For term definitions that define a class (such as rdfs:Class or owl:Class), it is recommended to include the following properties in your definition list if appropriate:
rdfs:seeAlso is a reference to some other resource that provides more information about the current term. Usually this can be used to link a class to the properties that instances of that class may have.rdfs:subClassOf is a property that declares the current class to inherit from the object. Logically, if something is an instance of the current class, it is also an instance of the superclass. For example, in AS2-Vocab, OrderedCollection is a subclass of Collection, so all OrderedCollection objects are also implied to have a type of Collection as well, even if not explicitly declared to be one.For term definitions that define a property (such as rdf:Property, owl:DatatypeProperty, or owl:ObjectProperty), it is recommended to include the following properties in your definition list if appropriate:
rdfs:domain is the Domain of the property. Logically, this implies that if a property p has a domain D, and a given resource has the property p, then that resource has a type of D. For example, in AS2-Vocab the domain of actor is Activity, so anything that has an actor is implied to be an Activity even if not explicitly declared to be one.rdfs:range is the Range of the property. Logically, this implies that if a property p has a range R, and a given resource is the value of p, then that resource has a type of R. For example, in AS2-Vocab the range of replies is Collection, so anything that is the value of replies is implied to be a Collection even if not explicitly declared to be one.property of owl:minCardinality has a content of 1 (with a datatype of xsd:nonNegativeInteger). In other words, a property with a minimum cardinality of 1 must have at least one value. If the property is not Required, then this statement can be amended to have a content of 0, or in other words, you are stating that this property must have at least 0 values (which allows having no values).property of owl:maxCardinality has a content of 1 (with a datatype of xsd:nonNegativeInteger). In other words, a property with a maximum cardinality of 1 can have at most one value. If the property is not Functional, then this statement can be omitted.rdfs:seeAlso is a reference to some other resource that provides more information about the current term. Usually this can be used to link a property to other related terms, such as term definitions that can be used as the value of this property.If the term definition contains JSON-LD keywords like @id, @vocab, @set, or @list within a <dd> element whose corresponding <dt> element's inner text exactly matches Range, then these keywords can be used to automatically generate a context document. Set the attribute excluded on the term definition to a non-empty value to exclude it from auto-generated context term definitions.
Source code for the script is available in make_definition.py, currently co-located in the FEP-888d folder.
The script will operate according to the following logic:
"},{"location":"fep/888d/#options-available","title":"Options available","text":"@id, @vocab, @set, and @list.fep-$SLUG.jsonld co-located within the FEP folder). Failure to read this path as valid JSON will lead to an empty context mapping.id and resource. These are considered \"term definitions\".s equal to the resource attribute of the element.term_type equal to the typeof attribute of the element.s rdf:type term_type.property attribute. These are considered \"property definitions\".p equal to the property attribute.o equal to the resource attribute. If not present, then set the object o equal to the href attribute. If not present, then set the object o equal to the content attribute. If not present, then set the object o equal to the inner text of the current element.lang equal to the lang attribute.datatype equal to the datatype attribute.s, p, o, lang, and datatype, making sure to expand any CURIEs according to the RDFa initial context.datatype or lang, then the object o is a Literal.o is an IRI reference.excluded attribute, then extract JSON-LD keywords and automatically generate a context term definition.term_name equal to the id attribute of the element.options mapping to keep track of extended term definitions.<dt> element with its corresponding child <dd> element.<dt> element whose inner text is exactly \"Range\". [TODO: something less fragile?]<dd> element for a resource attribute. If there is one, extract this IRI as type_iri.type_iri starts with xsd, then insert @type: type_iri into the options mapping.<dd> element for a substring @id. If found, then insert @type: @id into the options mapping.<dd> element for a substring @vocab. If found, then insert @type: @vocab into the options mapping.<dd> element for a substring @set. If found, then insert @container: @set into the options mapping.<dd> element for a substring @list. If found, then insert @container: @list into the options mapping.options mapping is not empty, then insert @id: s into the options mapping.options mapping is not empty, then map term_name to the options mapping. Otherwise, map term_name to the string s. This is considered a \"context term definition\".s is not a fragment identifier, then for each alternate output format in OUTPUT_FORMATS, serialize the current term graph to that format and write it to a file within a co-located subfolder with the name term_name.term_name set equal to the id attribute of the term definition.<dt> element with its corresponding child <dd> element.<dt> and <dd> element, write a definition list term and definition list definition.<pre>.Examples.title attribute is present, then write a paragraph with the text set to the value of title.lang attribute is present, then append its value immediately afterward.fep-$SLUG.$FORMAT. For example, if Turtle output is enabled, then fep-xxxx.ttl will be written. For JSON-LD output, use the current context mapping when serializing the FEP-wide graph. For HTML+RDFa or Markdown output, ignore these formats. (Outputting HTML can be done as part of a separate process of rendering the Markdown with a static site generator. Outputting the Markdown makes no sense since it is the source material and should not be overwritten.)fep-xxxx.jsonld.markdown on PyPI)beautifulsoup4)rdflib)markdownify)python-frontmatter)uv run instead.python fep/888d/make_definitions.py $SLUG will read term definitions from the contents of fep/$SLUG/fep-$SLUG.md, then generate alternate formats for the FEP. If your terms are defined with fragment identifiers (of the form https://w3id.org/fep/xxxx#term), then only the FEP itself will be generated in alternate formats. If your terms are defined with absolute identifiers (of the form https://w3id.org/fep/xxxx/term), then co-located subdirectories will be created as well, and singular term definitions will be generated in chosen output formats. If a context.jsonld document is present in the FEP folder, then it will be copied into the JSON-LD alternate format output. The current default output formats are:python fep/888d/make_definitions.py $SLUG -c will do everything in the above bullet point, except for copying context.jsonld. Instead, it will check for JSON-LD keywords and attempt to auto-generate a context document based on those keywords. If JSON-LD output is disabled, the script will generate only a context document instead of a combined context document and schema or ontology.(This section is non-normative.)
Using the same fictitious terms from the above examples:
SomeType is a term for some typeexampleA is a term for a property with some literal value (string, boolean, number)exampleB is a term for a property that links to another node on the graph (for example, another object)exampleC is a term for a property that is an ordered list of literal values that are specifically non-negative integersExcludedExample is a term for some class that is useful for ontology but not intended to be used for the context mappingWe can formulate the following machine-readable term definition blocks, which are intended to provide a range of examples of various RDFa syntactical constructs for demonstration purposes. Specifically, the following mechanisms are demonstrated:
resource with a typeof some classproperty with a specified langproperty has a value that is a resourceproperty where the object value is referred to by href instead of resource (for example, in an anchor link)property by including multiple tags@context, such as signaling that a property is required or functionalproperty has literal content instead of referring to a resource, and that this content can have its own datatype to which it can be coercedexcluded attributehttps://w3id.org/fep/888d/SomeType Label Some Type Comment Some type. Subclass of Object See also exampleA | exampleB | exampleC Is defined by FEP-888d \n\n{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n\n exampleA URI https://w3id.org/fep/888d/exampleA Label has example relation A with the literal value Comment A property with some literal value (string, boolean, number) Domain SomeType Range A literal value Required Yes Functional Yes Is defined by FEP-888d \n\n{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n\n exampleB URI https://w3id.org/fep/888d/exampleB Label has example relation B with Comment A property that links to another node on the graph (for example, another object) Domain SomeType Range An object (@id) Required No Functional No Is defined by FEP-888d \n\n{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n\n exampleC URI https://w3id.org/fep/888d/exampleC Label has an exampleC value of Comment A property that is an ordered list of literal values that are specifically non-negative integers Domain SomeType Range A list of non-negative integers (@list) Required No Functional Yes Is defined by FEP-888d \n\n{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n\n ExcludedExample URI https://w3id.org/fep/888d#ExcludedExample Label Excluded Example Comment A class that is useful for ontology but not intended to be used for the context mapping Subclass of Object Is defined by FEP-888d"},{"location":"fep/888d/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/888d/SomeType/","title":"SomeType","text":"URI : https://w3id.org/fep/888d/SomeType
Label : Some Type
Comment : Some type.
Subclass of : Object
See also : exampleA | exampleB | exampleC
Is defined by : FEP-888d
"},{"location":"fep/888d/SomeType/#examples","title":"Examples","text":"Example of a SomeType with properties exampleA, exampleB, exampleC
{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n"},{"location":"fep/888d/exampleA/","title":"exampleA","text":"URI : https://w3id.org/fep/888d/exampleA
Label : has example relation A with the literal value
Comment : A property with some literal value (string, boolean, number)
Domain : SomeType
Range : A literal value
Required : Yes
Functional : Yes
Is defined by : FEP-888d
"},{"location":"fep/888d/exampleA/#examples","title":"Examples","text":"Example of a SomeType with properties exampleA, exampleB, exampleC
{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n"},{"location":"fep/888d/exampleB/","title":"exampleB","text":"URI : https://w3id.org/fep/888d/exampleB
Label : has example relation B with
Comment : A property that links to another node on the graph (for example, another object)
Domain : SomeType
Range : An object (@id)
Required : No
Functional : No
Is defined by : FEP-888d
"},{"location":"fep/888d/exampleB/#examples","title":"Examples","text":"Example of a SomeType with properties exampleA, exampleB, exampleC
{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n"},{"location":"fep/888d/exampleC/","title":"exampleC","text":"URI : https://w3id.org/fep/888d/exampleC
Label : has an exampleC value of
Comment : A property that is an ordered list of literal values that are specifically non-negative integers
Domain : SomeType
Range : A list of non-negative integers (@list)
Required : No
Functional : Yes
Is defined by : FEP-888d
"},{"location":"fep/888d/exampleC/#examples","title":"Examples","text":"Example of a SomeType with properties exampleA, exampleB, exampleC
{\n \"@context\": \"https://w3id.org/fep/888d\",\n \"@type\": \"SomeType\",\n \"exampleA\": true,\n \"exampleB\": \"https://example.com/some-object\",\n \"exampleC\": [1, 1]\n}\n"},{"location":"fep/8967/","title":"FEP-8967: Generating link previews for attached links","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 2025-09-16 #681 Discussions codeberg"},{"location":"fep/8967/#summary","title":"Summary","text":"A common feature in social applications is to show users a rich preview of a link included in the content of a message or post, before the user clicks the link. Currently, applications like Mastodon generate link previews for the first link found in the content, without considering the publisher's possible intent. This FEP allows publishers to explicitly signal which links are intended for special processing, using the existing attachment model. Optionally, publishers can include their own link preview information so that trusting consumers can skip generating their own previews.
"},{"location":"fep/8967/#link-in-attachment","title":"Link in attachment","text":"The attachment model can be used to indicate that something performs a particular function as an extension of the main content. AS2-VOCAB defines attachment as \"semantically similar to attachments in email\" for something that \"potentially requires special handling\".
Publishers MAY include a Link in attachment to signal that this link should be processed semantically as an attached link preview card, appearing similarly to attached images, attached videos, or attached audio. The link attachment MUST have an href, indicating that it is a Link.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"attachment\": {\n \"href\": \"https://foo.example/\"\n }\n}\n Upon encountering an attachment that is a Link, consumers SHOULD show this link as \"attached\" to the object. At minimum, the href can be rendered directly, perhaps alongside an icon representing a link.
Publishers MAY include link preview information using the preview property:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sensitive\": \"as:sensitive\"\n }\n ],\n \"attachment\": {\n \"href\": \"https://foo.example/\",\n \"preview\": {\n \"type\": \"Article\",\n \"name\": \"Example Essay\",\n \"summary\": \"In which some information is provided...\",\n \"image\": {\n \"sensitive\": true,\n \"url\": {\n \"href\": \"https://cover-image.example/file.jpg\",\n \"mediaType\": \"image/jpg\",\n \"width\": 1200,\n \"height\": 630\n }\n },\n \"attributedTo\": {\n \"name\": \"The Author\",\n \"icon\": {\n \"url\": {\n \"href\": \"https://avatar.example/file.png\",\n \"mediaType\": \"image/png\",\n \"width\": 48,\n \"height\": 48\n }\n },\n \"url\": {\n \"href\": \"https://author.example/\",\n }\n }\n }\n }\n}\n The exact form of the preview and its processing model is out of scope of this FEP (as each consumer is free to render information according to their own design language and understanding), but some properties may be useful as equivalents of OpenGraph properties which are widely used for link previews:
name -- similar to og:title, indicate the preview card's primary text.summary -- similar to the og:description, indicate the preview card's secondary text.image -- similar to og:image, indicate the preview card's image.type -- similar to og:type, indicate the type of the target resource. This can be used to select an appropriate icon representing the resource.attributedTo -- loosely similar to article:author, music:musician, music:creator, book:author, and other such properties, indicate the preview card's attribution.name -- the name that should be attributedicon -- the icon that should be displayed alongside the attributed nameurl -- the link that should wrap the attributionConsumers that do not trust the publisher's provided preview information MAY generate their own preview through whichever means they find appropriate, such as for example fetching the link target and extracting OpenGraph information or HTML tags such as <title> or <meta>.
(This section is non-normative.)
"},{"location":"fep/8967/#attaching-objects-directly","title":"Attaching objects directly","text":"Rather than attaching a Link with an optional preview, resources can be attached directly by their id along with arbitrary optional claims.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"attachment\": {\n \"id\": \"https://foo.example/\",\n \"type\": \"Article\",\n \"name\": \"Example Essay\",\n // ...\n }\n}\n The difference between attaching an Object versus attaching a Link is that attaching an Object creates a direct relation between the current object and the attached object, whereas attaching a Link does not create this relation between two objects. For example, consider the difference between attaching an Image versus attaching a Link that targets an image. In most cases, directly attaching the Image is probably more appropriate. However, if the publisher attaches a Link instead, it might be because the link is present in the content and the publisher wishes to indicate this for special processing; for whatever reason, the publisher does not want to directly attach the Image. This depends on the specific details of the processing model, which is out of scope for this FEP.
href as a signal to generate link previews with their existing OpenGraph logic. In the future, Mastodon plans to stop automatically extracting the first link in content, but for now the first link is used as a fallback in case no attachment is present.CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/8a8e/","title":"Index","text":"hide: - navigation description: \"ActivityStreams defines the Object Type Event. In real-world applications, the event object immediately showed the need for extension. Applications featuring Event objects have often chosen to add additional attributes and clarifications (i.e., interpretations) in order to implement their particular use case. This proposal clarifies and extends the ActivityPub standard to address the needs that have arisen in real-world implementations. This includes guidelines for the minimal interoperable event, handling of RSVP (\"r\u00e9pondez s'il vous pla\u00eet\", i.e., attendee management, and side effects), attendee capacities, physical location addresses, virtual locations, timezone, and clarification of how to control the visibility of events in federation. These differences in how the aforementioned features are implemented have led to fragmentation in how events are published, discovered, and managed across platforms.\"
"},{"location":"fep/8a8e/#fep-8a8e-a-common-approach-to-using-the-event-object-type","title":"FEP-8a8e: A common approach to using the Event object type","text":"Authors Status Date received Tracking issue Discussions Repository Andr\u00e9 Menrath andre.menrath@posteo.de, les lesion@autistici.orgDRAFT 2025-04-23 #565 Discussions codeberg"},{"location":"fep/8a8e/#summary","title":"Summary","text":"ActivityStreams defines the Object Type Event. In real-world applications, the event object immediately showed the need for extension. Applications featuring Event objects have often chosen to add additional attributes and clarifications (i.e., interpretations) in order to implement their particular use case. This proposal clarifies and extends the ActivityPub standard to address the needs that have arisen in real-world implementations.
This includes guidelines for the minimal interoperable event, handling of RSVP (\"r\u00e9pondez s'il vous pla\u00eet\", i.e., attendee management, and side effects), attendee capacities, physical location addresses, virtual locations, timezone, and clarification of how to control the visibility of events in federation.
These differences in how the aforementioned features are implemented have led to fragmentation in how events are published, discovered, and managed across platforms.
"},{"location":"fep/8a8e/#what-is-not-covered","title":"What is not covered","text":"We do not cover recurrence, multiple-scheduled, and connected events.
For more information on that, check out for example: - RFC 5545 Section 3.8.5.3 - RFC 5545 Section 3.8.4.5
"},{"location":"fep/8a8e/#history","title":"History","text":"Fediverse applications, such as Mobilizon, Friendica, Gancio, and Hubzilla, have adopted varying approaches for handling ActivityPub events. Their different aims and the varying use cases have led to fragmentation in how events are published, discovered, and managed across federation.
Friendica and Hubzilla, for instance, follow the example set by ActivityStreams 2.0, using Invite for events, which seems to be very suited for smaller, more private gatherings, such as a birthday party. Invite seems natural when visibility is intentionally limited. On the other hand, applications like Mobilizon or Gancio have a focus on announcing public events and therefore chose to make use of the Create activity. Maybe another reason was that using creates eases interoperability with applications that do not support the Invite activity (yet). Furthermore, this may be justified because public events should as well be discoverable by anonymous actors, even by people who do not have an account in the Fediverse. This is achieved through federated event calendars, which are assembled by the instance administrators, who can add various federated event sources by following federated ActivityPub actors.
Within Mobilizon, for historical and internal reasons, the federated visibility of events is currently still controlled by whether a Group has published the Event or not, and whether the Event has also been announced at least once, rather than using to/cc (e.g., like Mastodon's \"public silent\"). In federation with other applications, this and other discrepancies led to issues, which have proven to be huge blockers. That's why this FEP sets out to define a common ground.
The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in RFC-2119.
The context of attributes: Attributes that have no context prefix are to be interpreted as being in the https://www.w3.org/ns/activitystreams context.
The ActivityStreams specification already defines an object type Event which is inheriting all properties from the base Object, so some properties useful for describing an event are already specified, but some others are missing, and an enhanced specification will help.
For an Event at least the following properties are REQUIRED:
name: A simple, human-readable, plain-text title for the event. HTML markup MUST NOT be included. Values for the name MUST NOT be an empty string and the name MAY be expressed using multiple language-tagged values.
startTime: The date and time describing the moment the event began or is scheduled to begin in the format as specified in the Activity Stream 2.0 specification. The startTime is not REQUIRED in case an event is postponed and a https://w3id.org/fep/8a8e/previousStartTime is set instead.
endTime: The date and time when the event ends or is scheduled to end in the format as specified in the Activity Stream 2.0 specification. The endTime MUST be a later date than the startTime. If the event is open-ended, it must include an end time at which applications (not humans) can treat the event as having ended.
organizers
{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/foo\",\n \"name\": \"New years party\",\n \"startTime\": \"2014-12-31T23:00:00Z\",\n \"endTime\": \"2015-01-01T06:00:00Z\",\n \"organizers\": null\n}\n"},{"location":"fep/8a8e/#events-with-open-end","title":"Events with Open End","text":"If the event is open-ended, or the endTime is not intended to be displayed to humans, the https://w3id.org/fep/8a8e/displayEndTime attribute SHOULD be used and set to false.
Receiving an Event with an endTime set and no https://w3id.org/fep/8a8e/displayEndTime set, it MUST be treated as if https://w3id.org/fep/8a8e/displayEndTime is set to true, requiring the endTime to be displayed.
If an Event is received with no endTime set, the Event MUST be treated as if https://w3id.org/fep/8a8e/displayEndTime were set to false to avoid misleading users. In such cases, the receiving application MAY infer an appropriate endTime based on its requirements.
This section is non-normative.
The https://w3id.org/fep/8a8e/timezone property is introduced to support time zone information for events, enabling consistent handling of local \"wall times\".
It is important to note that the \"time-offset\" component in startTime and endTime property does not correlate to time-zones, and while times that include the \"time-offset\" component work well for timestamps, they cannot be reliably converted to and from local \"wall times\" without additional information and processing. https://www.w3.org/TR/activitystreams-core/#dates
For this reason, if the Event is primarily conducted in a single specific time-zone, the application SHOULD set the https://w3id.org/fep/8a8e/timezone.
In case the https://w3id.org/fep/8a8e/timezone property is specified it MUST be a specific IANA time zone identifier included in the IANA Time Zone Database (often called tz or zoneinfo, see https://www.w3.org/TR/timezone/#time-zone-identifiers).
{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/foo\",\n \"name\": \"New years party\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00+01:00\",\n \"endTime\": \"2015-01-01T04:00:00+01:00\",\n \"timezone\": \"Europe/Rome\"\n}\n"},{"location":"fep/8a8e/#location","title":"Location","text":"ActivityStreams provides a flexible framework for representing locations, as outlined in this section of the specification. However, its flexibility can sometimes lead to ambiguity.
This section aims to establish a clear and consistent format for representing the locations of events, whether they are physical, virtual, or mixed.
Rather than creating a new schema, this specification adopts the location property from Schema.org with some adjustments to better suite federated event handling:
Exclusion of Text as a valid type: Unlike the original Schema.org definition, Text is not a permitted type for location and MUST NOT be used. If only a name is required to describe a location, the Place type can be used.
Support for multiple locations: To specify mixed-format events (e.g., hybrid online and offline events), the location property can contain an array of locations. Each entry in the array can represent either a physical Place or a VirtualLocation.
For adding information about a physical location of an Event the Place object within the events location property SHOULD be extended via the attribute address which is of type https://schema.org/address or https://schema.org/Place MAY be used directly.
This allows the address to be given as a simple text input as well as a structured https://schema.org/PostalAddress. Applications MUST be able to transmogrify both incoming address types to their internal representation.
It is RECOMMENDED to always provide geolocation data via longitude and latitude, as filtering by these properties is crucial in federation.
Note Even if users do not provide this data directly, attempting to look up the geolocation of a physical location once on the source side can save a lot of resources caused by multiple lookups on the receivers in federation.
Example with pure text address:
{\n \"@context\": [\n \"https://schema.org\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/new-year-party\",\n \"name\": \"New years party\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00+01:00\",\n \"endTime\": \"2015-01-01T04:00:00+01:00\",\n \"location\": {\n \"type\": \"Place\",\n \"name\": \"Foo Bar\",\n \"address\": \"15 Fediverse Street, 8010 Fediverse Town, Austria\",\n \"latitude\": 47.06829,\n \"longitude\": 15.45634\n }\n}\n Example with address of type https://schema.org/PostalAddress:
{\n \"@context\": [\n \"https://schema.org\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/new-year-party\",\n \"name\": \"New years party\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"location\": {\n \"id\": \"\",\n \"type\": \"Place\",\n \"name\": \"Foo Bar\",\n \"address\": {\n \"type\": \"PostalAddress\",\n \"addressCountry\": \"Austria\",\n \"addressLocality\": \"Fediverse Town\",\n \"addressRegion\": \"Steiermark\",\n \"postalCode\": \"8010\",\n \"streetAddress\": \"15 Fediverse Street\"\n },\n \"latitude\": 47.077892,\n \"longitude\": 15.460744\n }\n}\n"},{"location":"fep/8a8e/#virtual","title":"Virtual","text":"An online Location SHOULD be represented by using a type VirtualLocation as specified in https://schema.org/VirtualLocation.
Applications SHOULD at least set and be able to make use of name and url properties.
{\n \"@context\": [\n \"https://schema.org\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/new-year-party\",\n \"name\": \"Fediverse Moderation Meeting\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"location\": {\n \"type\": \"VirtualLocation\",\n \"name\": \"Jitsi Meet Meeting Link\",\n \"url\": \"https://jitsi.example.org/fediverse-moderation-meeting\"\n }\n}\n"},{"location":"fep/8a8e/#hybrid","title":"Hybrid","text":"Using multiple entries in location makes it easy to define multiple virtual and physical locations.
When not supporting multiple entries in location applications MUST use the first supported item.
{\n \"@context\": [\n \"https://schema.org\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/new-year-party\",\n \"name\": \"Discussion about the Fediverse\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"location\": [\n {\n \"type\": \"Place\",\n \"name\": \"Fediverse University\",\n \"url\": \"https://university.example.com\",\n \"address\": \"15 Fediverse Street, 1337 Fediverse Town, Fediverse Country\"\n },\n {\n \"type\": \"VirtualLocation\",\n \"name\": \"Big Blue Button Participation Link\",\n \"url\": \"https://bbb.example.com/meeting-room-1\"\n },\n {\n \"type\": \"VirtualLocation\",\n \"name\": \"Live Stream\",\n \"url\": \"https://peertube.example.com/fediverse-discussion\"\n }\n ]\n}\n"},{"location":"fep/8a8e/#event-status","title":"Event status","text":"To indicate the status of an Event, the https://w3id.org/fep/8a8e/eventStatus property MUST be used. As a fallback, it is RECOMMENDED to also support the status property, especially for incoming remote events, which might contain a value from https://www.w3.org/2002/12/cal/ical#status.
The possible values for the https://w3id.org/fep/8a8e/eventStatus are: - EventCancelled - EventMovedOnline - EventPostponed - EventRescheduled - EventScheduled - EventTentative
Note https://w3id.org/fep/8a8e/eventStatus is almost identical to https://schema.org/eventStatus, however it has an additional status type for the event being tentative, which is leaned on iCalendar's TENTATIVE.
If the event creator or the event creator's application does not handle joins via ActivityPub it MUST always respond to Join activities with an Ignore response, where the attribute object MUST either contain the ID of the Join activity, or contain a Join object with the target being set to the event-objects ID, where the former is preferred.
This SHOULD be made clear beforehand by setting https://w3id.org/fep/8a8e/joinMode to none.
If an application does not handle joins of an Event via ActivityPub, but knows of an external URL which handles the attendee management https://w3id.org/fep/8a8e/joinMode SHOULD be set to external. If joinMode is set to external, also https://w3id.org/fep/8a8e/externalParticipationUrl SHOULD be set.
Note This is compatible with Mobilizon's definition of joinMode, but it is not identical.
{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/foo\",\n \"name\": \"New years party\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"joinMode\": \"external\",\n \"externalParticipationUrl\": \"https://example.org/foo#register\"\n}\n Note externalParticipationUrl could also be a mailto-URI, which is a way of handling event participants that is still used quite frequently by many organizers.
{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/fediverse-workshop\",\n \"organizers\": null,\n \"name\": \"Fediverse Workshop\",\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2025-01-01T04:00:00-08:00\",\n \"joinMode\": \"external\",\n \"externalParticipationUrl\": \"mailto:fediverse-workshop@example.org\"\n}\n"},{"location":"fep/8a8e/#indicating-visibility-and-target-audience-of-rsvp-requests","title":"Indicating visibility and target audience of RSVP requests","text":"Who the participants of an event are should possibly not be public, but should, for example, only be visible to the organisers of the event, in other cases also to other confirmed participants, or, for example, completely anonymously. An Event SHOULD provide information about the visibility scope of valid Join requests targeting the event via utilizing https://w3id.org/fep/8a8e/requiredJoinVisibility.
To specify the maximum number of attendees for an Event, applications SHOULD use the https://schema.org/maximumAttendeeCapacity.
Remaining attendee capacity SHOULD be shown indirectly via using the Collection https://w3id.org/fep/8a8e/attendees. This is a list of all event attendees. Every Actor that has accepted an Invite containing the Event as an object or which Join targeting the Event has been accepted is added as a side effect. Note that also other object types other than Actor (and it's subclasses) might be part of that collection, e.g. when an application allows for attendees to register by e-mail. In that case using https://schema.org/Person or https://schema.org/Organization is RECOMMENDED. The attendees collection MUST be either an OrderedCollection or a Collection and MAY be filtered by the privileges of an authenticated user, or as appropriate if no authentication is given.
{\n \"@context\": [\n \"https://schema.org\",\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/foo-bar-party\",\n \"name\": \"New years party\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"maximumAttendeeCapacity\": 100,\n \"attendees\": {\n \"type\": \"Collection\",\n \"id\": \"https://example.org/foo-bar-party/attendees\",\n \"totalItems\": 84\n }\n}\n"},{"location":"fep/8a8e/#attendees-collection","title":"Attendees Collection","text":"Every Event MAY have an attendees collection. This is a list of all actors that meet one or more of the following conditions, added as a side effect.
Join activity with this object as the object property that has been answered with an Accept.Invite activity from the Event's owner (where the Event is specified as the object property) with an Accept activity.The attendees collection MUST be either an OrderedCollection or a Collection and MAY be filtered on privileges of an authenticated user or as appropriate when no authentication is given. This collection MAY have the totalItems set regardless of authentication. If https://schema.org/maximumAttendeeCapacity is used totalItems SHOULD be public to disclose remaining capacity.
If the event has an image in the style of a flyer or a share-pic which is representing the whole event (i.e., is also containing at least the name and start-date in the image) this image SHOULD be set using the image attribute.
If the Event has a particular banner image, this image SHOULD be set as the first attachment of type Image (type Document is also valid but not RECOMMENDED). To indicate that an image from within the attachment attribute of an Event should be preferred over the flyer/share-pic image(s) within image attribute, for the use of list pages and header images, the attribute https://w3id.org/fep/8a8e/isBannerImage SHOULD be set.
Federated event calendars usually have thumbnail images with fixed aspect ratios. Therefore, https://docs.joinmastodon.org/spec/activitypub/#focalPoint SHOULD be set.
Note Providing multiple versions of banner images and specifying width and height gives receiving applications the most flexibility to choose the most appropriate one based on aspect ratio.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\": \"http://joinmastodon.org/ns#\",\n \"focalPoint\": {\n \"@container\": \"@list\",\n \"@id\": \"toot:focalPoint\"\n }\n }\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/new-year-party\",\n \"name\": \"New years party\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"image\": {\n \"type\": \"Image\",\n \"mediaType\": \"image/jpeg\",\n \"url\": \"https://example.com/images/new-year-party-flyer.png\",\n \"focalPoint\": [\n -0.55,\n 0.43\n ]\n }\n}\n"},{"location":"fep/8a8e/#event-categories","title":"Event Categories","text":"Federated event platforms benefit from a shared understanding of general event categories. These categories serve a distinct purpose from user-generated tags or hashtags. While hashtags are typically entered freely and reflect a specific language, event categories are drawn from a limited, generalized set, making them more suitable for tasks like filtering events in aggregated or joined calendars.
To represent categories, this FEP introduces the property https://w3id.org/fep/8a8e/category.
In most cases, a single category should be sufficient. However, multiple categories may be included when relevant. Applications SHOULD utilize at least the following recommended set of event categories:
Note Implementing applications may extend this list or allow user-generated categories. However, such additions may not be understood by all consuming platforms and could default to a generic or \"unknown\" category.
"},{"location":"fep/8a8e/#example-of-an-event-with-a-single-category","title":"Example of an event with a single category","text":"{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/event/1\",\n \"name\": \"Demonstration againt fascism\",\n \"startTime\": \"2014-05-01T12:00:00-08:00\",\n \"startTime\": \"2014-05-01T18:00:00-08:00\",\n \"organizer\": null,\n \"category\": \"MOVEMENTS_POLITICS\"\n}\n"},{"location":"fep/8a8e/#example-of-an-event-with-multiple-categories","title":"Example of an event with multiple categories","text":"{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/open-mic-jam\",\n \"name\": \"Open Mic Jam at Sunny's\",\n \"startTime\": \"2014-12-12T20:00:00-08:00\",\n \"endTime\": \"2014-12-12T23:00:00-08:00\",\n \"organizer\": null,\n \"category\": [\n \"MUSIC\",\n \"CREATIVE_JAM\"\n ]\n}\n"},{"location":"fep/8a8e/#discoverability","title":"Discoverability","text":"Fediverse applications that feature Event objects MAY provide public calendars, search functions or grouped visualization pages (e.g., for tags or categories) that display events from multiple federated sources. In both cases these MUST never include events from federation that do not have https://www.w3.org/ns/activitystreams#Public (also as:Public or Public is valid) set in to. However, they MAY appear within a local representation of a remote actor, if Public set in cc.
The discoverability of an incoming event from federation MUST NOT be controlled by the type of the actor set in attributedTo or by the Activity the event was received (e.g., Announce, Create).
Events SHOULD indicate their organizers by using https://w3id.org/fep/8a8e/organizers. The organizers collection helps provide clarity about the event's management, supports federated discovery, and improves interoperability across platforms.
The value of the organizers field MUST be either an OrderedCollection or a Collection. The items within this collection MAY include actors, or links. If the organizer is not an ActivityPub entity (e.g., a website or organization without an actor), it is recommended to either use a Link or https://schema.org/Person or https://schema.org/Organization.
{\n \"@context\": [\n \"https://schema.org\",\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/foo-bar-party\",\n \"name\": \"New Year's Party\",\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"organizers\": {\n \"type\": \"OrganizersCollection\",\n \"id\": \"https://example.org/foo-bar-party/organizers\",\n \"totalItems\": 3,\n \"first\": {\n \"type\": \"CollectionPage\",\n \"partOf\": \"https://example.org/foo-bar-party/organizers\",\n \"items\": [\n \"https://example.social/users/johnmastodon\",\n \"https://example.org/somewebsite\",\n {\n \"type\": \"Organization\",\n \"id\": \"https://example.events/actors/eventorganizer\",\n \"context\": \"https://example.events/actors/eventorganizer/accept/example.org-foo-bar-party-offer\"\n }\n ]\n }\n }\n}\n"},{"location":"fep/8a8e/#explicit-non-disclosure","title":"Explicit Non-Disclosure","text":"If the event\u2019s organizers are intentionally not disclosed, the organizers field MUST be set to null, have totalItems: 0, or contain an empty items array. This explicitly indicates a deliberate choice to withhold organizer information and takes precedence over any assumptions about organizers based on other fields. In such a case, when receiving an event, clients MUST NOT infer organizers from other event data.
attributedTo","text":"If the organizers property is omitted, it MAY be inferred that the actors in the attributedTo property are the event's organizers. For events with multiple organizers, it is RECOMMENDED to provide an organizers collection to avoid and ensure compatibility with applications that do not support arrays in attributedTo.
Every actor that has published Events SHOULD have a https://w3id.org/fep/8a8e/upcomingEvents collection to list their future or ongoing events. This collection MUST follow these rules:
Collection Type: The collection MUST be an OrderedCollection or one of its subtypes.
Ordering: Events MUST be ordered in ascending order by their startTime, with the earliest events appearing first.
Items: Each item in the collection MUST meet the following criteria:
Event or one of its subtypes. Event's endTime MUST NOT be earlier than the current time, i.e., the event is ongoing or scheduled in the future. Actor is included in the Event's organizers collection.Actor owns the Event, i.e., is part of the Event's attributedTo.Event is in the actor's shares collection, and the Event shares the same host with the actor. This use case is primarily intended to cover relay and group Actors.Note that the terms joinMode and externalParticipationUrl are compatible with and also leaned on the extensions defined by Mobilizon.
https://w3id.org/fep/8a8e/displayEndTime Label Whether to display the endTime Comment A property that defines whether the end time of an event should be displayed. Domain displayEndTime Range Boolean Is defined by FEP-8a8e \n \n {\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"displayEndTime\": false\n }\n \n upcomingEvents URI https://w3id.org/fep/8a8e/upcomingEvents Label Ordered collection of upcoming Events Comment An ActivityStreams OrderedCollection of Event objects that have a startTime property in the future, sorted by startTime with the earliest first. Range An OrderedCollection (@id) Is defined by FEP-8a8e \n \n {\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Organization\",\n \"upcomingEvents\": {\n \"type\": \"OrderedCollection\",\n \"items\": [\n { \"type\": \"Event\", \"startTime\": \"2025-06-01T12:00:00Z\" },\n { \"type\": \"Event\", \"startTime\": \"2025-07-15T15:30:00Z\" }\n ]\n }\n }\n \n OrganizersCollection URI https://w3id.org/fep/8a8e/OrganizersCollection Label An Event's Organizer Collection Comment Inherits all properties from https://www.w3.org/ns/activitystreams#Collection with the addition that the items may also include https://schema.org/Person or https://schema.org/Organization. Subclass of Object See also organizers Is defined by FEP-8a8e \n \n {\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sc\": \"http://schema.org/\"\n }\n ],\n \"type\": \"OrganizersCollection\",\n \"items\": [\n { \"type\": \"Group\", \"name\": \"ActivityPub Group Actor\", \"id\": \"https://example.org/actors/group1\"},\n { \"type\": \"Link\", \"href\": \"https://organizer1.example.org\"},\n { \"type\": \"sc:Person\", \"name\": \"Alice\" },\n { \"type\": \"Organization\", \"name\": \"Event Co.\" }\n ]\n }\n \n organizers URI https://w3id.org/fep/8a8e/organizers Label Organizers Collection Comment An ActivityStreams Collection collection that lists the entities that are disclosed to as organizers of an event. If set to null this explicitly indicates a deliberate choice to withhold organizer information. Range OrganizersCollection (SubType of of the Collection or OrderedCollection ActivityStreams Type) or null Required Yes Functional No Is defined by FEP-8a8e \n \n {\n \"@context\": [\n \"https://schema.org\",\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sc\": \"http://schema.org/\"\n }\n ],\n \"type\": \"Event\",\n \"organizers\": {\n \"type\": \"OrganizersCollection\",\n \"totalItems\": 4,\n \"items\": [\n { \"type\": \"Person\", \"name\": \"ActivityPub Person Actor\", \"id\": \"https://example.org/actors/1\"},\n { \"type\": \"Link\", \"href\": \"https://organizer1.example.org\"},\n { \"type\": \"sc:Person\", \"name\": \"Alice\" },\n { \"type\": \"sc:Organization\", \"name\": \"Event Co.\" }\n ]\n }\n }\n \n AttendeesCollection URI https://w3id.org/fep/8a8e/AttendeesCollection Label A Collection of the Event's attendees Comment Inherits all properties from https://www.w3.org/ns/activitystreams#Collection with the addition that the items may also include https://schema.org/Person or https://schema.org/Organization. The items of the AttendeesCollection are entities that are confirmed attendees by an Events organizer(s). It contains all ActivityPub actors that meet one or more of the following conditions, added as a side effect. \n \n {\n \"@context\": [\n \"https://schema.org\",\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sc\": \"https://schema.org/\"\n }\n ],\n \"type\": \"AttendeesCollection\",\n \"totalItems\": 2,\n \"id\": \"https://example.org/events/alice-birthday-party\"\n \"items\": [\n { \"type\": \"Person\", \"name\": \"Bob\", \"id\": \"https://example.org/actors/bob\"},\n { \"type\": \"sc:Person\", \"name\": \"Alice\", \"email\": \"alice@example.org\"}\n ]\n }\n \n attendees URI https://w3id.org/fep/8a8e/attendees Label Attendees of an Event Comment Is defined by FEP-8a8e Range Collection (ActivityStreams Type) Required No Functional No \n \n {\n \"@context\": [\n \"https://schema.org\",\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sc\": \"https://schema.org#\"\n }\n ],\n \"type\": \"Event\",\n \"attendees\": {\n \"type\": \"OrganizersCollection\",\n \"totalItems\": 4,\n \"items\": [\n { \"type\": \"Person\", \"name\": \"ActivityPub Person\", \"id\": \"https://example.org/actors/1\"},\n { \"type\": \"Link\", \"href\": \"https://organizer1.example.org\"},\n { \"type\": \"sc:Person\", \"name\": \"Alice\" },\n { \"type\": \"sc:Organization\", \"name\": \"Event Co.\" }\n ]\n }\n }\n \n timezone URI https://w3id.org/fep/8a8e/timezone Label The timezone of an Event Comment Indicates the timezone for which the time(s) indicated in the event are given. The value provided should be among those listed in the IANA Time Zone Database. Is defined by FEP-8a8e Range IANA Time Zone identifier Required No Functional No \n \n {\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/events/new-years-party\",\n \"name\": \"New years party\",\n \"startTime\": \"2014-12-31T23:00:00Z\",\n \"endTime\": \"2015-01-01T06:00:00Z\",\n \"timezone\": \"Europe/Vienna\",\n \"organizers\": null\n }\n \n joinMode URI https://w3id.org/fep/8a8e/joinMode rdfs:label Join mode rdfs:comment Indicator of how new members may be able to join an event. Accepted values: free, restricted, external, none, invite. If external, you must also set externalParticipationUrl. rdfs:domain as:Event rdfs:range A string rdfs:isDefinedBy FEP\u20118a8e \n{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"url\": \"http://example.org/events/1234\",\n \"joinMode\": \"restricted\"\n}\n\n \n{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"url\": \"http://example.org/events/1234\",\n \"joinMode\": \"external\",\n \"externalParticipationUrl\": \"https://www.escample.org/events/1234/participate\"\n}\n\n requiredJoinVisibility Label Required Join Visibility Comment Specifies the minimum audience that must be addressed in a valid Join activity related to the event. This can include individual actors, groups, the as:Public collection, or any other URI. The Join activity must be addressed accordingly (e.g., using to, cc, bto, or audience). Domain as:Event Range A list of any addressed targets, i.e. URIs (@list) Is defined by FEP-8a8e \n{\n \"@context\": [\n \"https://schema.org\",\n \"https://https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/yoga-workshop\",\n \"name\": \"Yoga Workshop with Alice and Bob\",\n \"startTime\": \"2014-12-12T18:00:00-08:00\",\n \"endTime\": \"2014-12-12T19:30:00-08:00\",\n \"attributedTo\": \"https://example.org/groups/fediyoga\",\n \"organizers\": {\n \"type\": \"OrganizersCollection\",\n \"id\": \"https://example.org/yoga-workshop/organizers\",\n \"totalItems\": 3,\n \"first\": {\n \"type\": \"CollectionPage\",\n \"partOf\": \"https://example.org/yoga-workshop/organizers\",\n \"items\": [\n \"https://example.org/users/bob\",\n \"https://example.org/users/alice\",\n \"https://example.org/groups/fediyoga\"\n ]\n },\n \"joinMode\": \"restricted\",\n \"requiredJoinVisibility\": [\n \"https://example.org/yoga-workshop/organizers\"\n ]\n}\n externalParticipationUrl URI https://w3id.org/fep/8a8e/externalParticipationUrl rdfs:label External participation URL rdfs:comment A URL that points to an external platform where people can join the event or where they can buy tickets for the event. Required if joinMode is set to external. rdfs:domain as:Event rdfs:range xsd:anyURI rdfs:isDefinedBy FEP\u20118a8e \n{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"url\": \"http://example.org/events/1234\",\n \"joinMode\": \"external\",\n \"externalParticipationUrl\": \"https://www.meetup.com/somegroup/events/00000/\"\n}\n\n isBannerImage URI https://w3id.org/fep/8a8e/isBannerImage rdfs:label Image is a banner image rdfs:comment Whether an image is an (events) banner image. rdfs:domain as:Image rdfs:range Boolean rdfs:isDefinedBy FEP\u20118a8e \n{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\": \"http://joinmastodon.org/ns#\",\n \"focalPoint\": {\n \"@container\": \"@list\",\n \"@id\": \"toot:focalPoint\"\n }\n }\n ],\n \"type\": \"Event\",\n \"id\": \"https://example.org/new-year-party\",\n \"name\": \"New years party\",\n \"organizers\": null,\n \"startTime\": \"2014-12-31T23:00:00-08:00\",\n \"endTime\": \"2015-01-01T04:00:00-08:00\",\n \"image\": {\n \"type\": \"Image\",\n \"mediaType\": \"image/jpeg\",\n \"url\": \"https://example.com/images/new-year-party-flyer.png\",\n \"focalPoint\": [\n -0.55,\n 0.43\n ]\n },\n \"attachment\": [\n {\n \"type\": \"Image\",\n \"mediaType\": \"image/jpeg\",\n \"url\": \"https://example.com/images/new-year-party-banner.png\",\n \"width\": 1000,\n \"height\": 500,\n \"isBannerImage\": true\n }\n ]\n}\n\n eventStatus URI https://w3id.org/fep/8a8e/eventStatus Label The events status is Comment An eventStatus of an event represents its status; particularly useful when an event is cancelled or rescheduled. Domain Event Range EventStatusType (@vocab) Required No Functional Yes See also EventCancelled | EventScheduled | EventTentative | EventMovedOnline | EventPostponed | EventRescheduled Is defined by FEP-8a8e \n\n{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"id\": \"https://domain.example/events/0\",\n \"type\": \"Event\",\n \"eventStatus\": \"EventScheduled\"\n}\n\n EventStatusType URI https://w3id.org/fep/8a8e/8a8e Label 8a8e Comment EventStatusType is an enumeration type whose instances represent several states that an Event may be in. Subclass of https://schema.org/EventStatusType See also eventStatus Is defined by FEP-8a8e EventScheduled URI https://w3id.org/fep/8a8e/EventScheduled Label The event is scheduled. Comment The event is normally scheduled and planned. Subclass of EventStatusType See also eventStatus Is defined by FEP-8a8e EventCancelled URI https://w3id.org/fep/8a8e/EventPostponed Label The event has been postponed. Comment The event has been postponed and no new date has been set. The event's previousStartTime should be set. Subclass of EventStatusType See also eventStatus Is defined by FEP-8a8e EventRescheduled URI https://w3id.org/fep/8a8e/EventRescheduled Label The event has been rescheduled. Comment The event's previousStartTime should be set to the old date and the startTime should be set to the event's new start date. Subclass of EventStatusType See also eventStatus Is defined by FEP-8a8e EventCancelled URI EventPostponed | EventRescheduled https://w3id.org/fep/8a8e/EventCancelled Label The event has been cancelled. Comment The event has been cancelled. Subclass of EventStatusType See also eventStatus Is defined by FEP-8a8e EventCancelled URI https://w3id.org/fep/8a8e/EventCancelled Label The event has been cancelled. Comment The event has been cancelled. Subclass of EventStatusType See also eventStatus Is defined by FEP-8a8e EventTentative URI https://w3id.org/fep/8a8e/EventTentative Label The event is tentative Comment The event is currently being planned but not confirmed. Subclass of EventStatusType See also eventStatus Is defined by FEP-8a8e EventMovedOnline URI https://w3id.org/fep/8a8e/EventMovedOnline Label The event moved online. Comment Indicates that the event was changed to allow online participation. Subclass of EventStatusType See also eventStatus Is defined by FEP-8a8e previousStartTime URI https://w3id.org/fep/8a8e/previousStartTime Label The event previous start time. Comment The events previous start time is the old start time before an event got postponed or rescheduled. Domain Event Range xsd:dateTime (@vocab) See also EventPostponed | EventRescheduled Is defined by FEP-8a8e category URI https://w3id.org/fep/8a8e/category Label The category of an Event Comment Recommended event categories include: ARTS, AUTO_BOAT_AIR, BOOK_CLUBS, BUSINESS, CAUSES, CLIMATE_ENVIRONMENT, COMMUNITY, COMEDY, CRAFTS, CREATIVE_JAM, DIY_MAKER_SPACES, FAMILY_EDUCATION, FASHION_BEAUTY, FESTIVALS, FILM_MEDIA, FOOD_DRINK, GAMES, INCLUSIVE_SPACES, LANGUAGE_CULTURE, LEARNING, LGBTQ, MEETING, MEDITATION_WELLBEING, MOVEMENTS_POLITICS, MUSIC, NETWORKING, OUTDOORS_ADVENTURE, PARTY, PERFORMING_VISUAL_ARTS, PETS, PHOTOGRAPHY, SCIENCE_TECH, SPIRITUALITY_RELIGION_BELIEFS, SPORTS, THEATRE, WORKSHOPS_SKILL_SHARING Domain Event Range xsd:string (@list) Is defined by FEP-8a8e"},{"location":"fep/8a8e/#credits","title":"Credits","text":"This work would not have been possible without the support of NLnet foundation and NGI Zero.
Thanks are also expressed to the people at OFFDEM who laid out the beginnings of this document.
Special thanks go to Laurin Weger, who was available for many time-consuming discussions. Further thanks go to the Cloudfest Hackathon 2025 for providing space and a platform for further discussion of this FEP and especially to Andreas Heigl for his rich feedback and ideas.
"},{"location":"fep/8a8e/#copyright","title":"Copyright","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/8a8e/AttendeesCollection/","title":"AttendeesCollection","text":"URI : https://w3id.org/fep/8a8e/AttendeesCollection
Label : A Collection of the Event's attendees
Comment : Inherits all properties from https://www.w3.org/ns/activitystreams#Collection with the addition that the items may also include https://schema.org/Person or https://schema.org/Organization. The items of the AttendeesCollection are entities that are confirmed attendees by an Events organizer(s). It contains all ActivityPub actors that meet one or more of the following conditions, added as a side effect.
The items MAY be filtered on privileges of an authenticated user or as appropriate when no authentication is given. This collection *SHOULD* have the totalItems set in any case.
Subclass of : Object
See also : attendees
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/AttendeesCollection/#examples","title":"Examples","text":"Example of an AttendeesCollection with different items
json { \"@context\": [ \"https://schema.org\", \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\", { \"sc\": \"https://schema.org/\" } ], \"type\": \"AttendeesCollection\", \"totalItems\": 2, \"id\": \"https://example.org/events/alice-birthday-party\" \"items\": [ { \"type\": \"Person\", \"name\": \"Bob\", \"id\": \"https://example.org/actors/bob\"}, { \"type\": \"sc:Person\", \"name\": \"Alice\", \"email\": \"alice@example.org\"} ] }
URI : https://w3id.org/fep/8a8e/EventCancelled
Label : The event has been cancelled.
Comment : The event has been cancelled.
Subclass of : EventStatusType
See also : eventStatus
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/EventMovedOnline/","title":"EventMovedOnline","text":"URI : https://w3id.org/fep/8a8e/EventMovedOnline
Label : The event moved online.
Comment : Indicates that the event was changed to allow online participation.
Subclass of : EventStatusType
See also : eventStatus
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/EventPostponed/","title":"EventPostponed","text":"URI : https://w3id.org/fep/8a8e/EventPostponed
Label : The event has been postponed.
Comment : The event has been postponed and no new date has been set. The event's previousStartTime should be set.
Subclass of : EventStatusType
See also : eventStatus
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/EventRescheduled/","title":"EventRescheduled","text":"URI : https://w3id.org/fep/8a8e/EventRescheduled
Label : The event has been rescheduled.
Comment : The event's previousStartTime should be set to the old date and the startTime should be set to the event's new start date.
Subclass of : EventStatusType
See also : eventStatus
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/EventScheduled/","title":"EventScheduled","text":"URI : https://w3id.org/fep/8a8e/EventScheduled
Label : The event is scheduled.
Comment : The event is normally scheduled and planned.
Subclass of : EventStatusType
See also : eventStatus
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/EventStatusType/","title":"EventStatusType","text":"URI : https://w3id.org/fep/8a8e/8a8e
Label : 8a8e
Comment : EventStatusType is an enumeration type whose instances represent several states that an Event may be in.
Subclass of : https://schema.org/EventStatusType
See also : eventStatus
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/EventTentative/","title":"EventTentative","text":"URI : https://w3id.org/fep/8a8e/EventTentative
Label : The event is tentative
Comment : The event is currently being planned but not confirmed.
Subclass of : EventStatusType
See also : eventStatus
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/OrganizersCollection/","title":"OrganizersCollection","text":"URI : https://w3id.org/fep/8a8e/OrganizersCollection
Label : An Event's Organizer Collection
Comment : Inherits all properties from https://www.w3.org/ns/activitystreams#Collection with the addition that the items may also include https://schema.org/Person or https://schema.org/Organization.
Subclass of : Object
See also : organizers
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/OrganizersCollection/#examples","title":"Examples","text":"Example of an OrganizersCollection with different items
json { \"@context\": [ \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\", { \"sc\": \"http://schema.org/\" } ], \"type\": \"OrganizersCollection\", \"items\": [ { \"type\": \"Group\", \"name\": \"ActivityPub Group Actor\", \"id\": \"https://example.org/actors/group1\"}, { \"type\": \"Link\", \"href\": \"https://organizer1.example.org\"}, { \"type\": \"sc:Person\", \"name\": \"Alice\" }, { \"type\": \"Organization\", \"name\": \"Event Co.\" } ] }
URI : https://w3id.org/fep/8a8e/attendees
Label : Attendees of an Event
Comment :
Is defined by : FEP-8a8e
Range : Collection (ActivityStreams Type)
Required : No
Functional : No
"},{"location":"fep/8a8e/attendees/#examples","title":"Examples","text":"Example usage of attendees
json { \"@context\": [ \"https://schema.org\", \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\", { \"sc\": \"https://schema.org#\" } ], \"type\": \"Event\", \"attendees\": { \"type\": \"OrganizersCollection\", \"totalItems\": 4, \"items\": [ { \"type\": \"Person\", \"name\": \"ActivityPub Person\", \"id\": \"https://example.org/actors/1\"}, { \"type\": \"Link\", \"href\": \"https://organizer1.example.org\"}, { \"type\": \"sc:Person\", \"name\": \"Alice\" }, { \"type\": \"sc:Organization\", \"name\": \"Event Co.\" } ] } }
URI : https://w3id.org/fep/8a8e/category
Label : The category of an Event
Comment : Recommended event categories include: ARTS, AUTO_BOAT_AIR, BOOK_CLUBS, BUSINESS, CAUSES, CLIMATE_ENVIRONMENT, COMMUNITY, COMEDY, CRAFTS, CREATIVE_JAM, DIY_MAKER_SPACES, FAMILY_EDUCATION, FASHION_BEAUTY, FESTIVALS, FILM_MEDIA, FOOD_DRINK, GAMES, INCLUSIVE_SPACES, LANGUAGE_CULTURE, LEARNING, LGBTQ, MEETING, MEDITATION_WELLBEING, MOVEMENTS_POLITICS, MUSIC, NETWORKING, OUTDOORS_ADVENTURE, PARTY, PERFORMING_VISUAL_ARTS, PETS, PHOTOGRAPHY, SCIENCE_TECH, SPIRITUALITY_RELIGION_BELIEFS, SPORTS, THEATRE, WORKSHOPS_SKILL_SHARING
Domain : Event
Range : xsd:string (@list)
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/displayEndTime/","title":"displayEndTime","text":"URI : https://w3id.org/fep/8a8e/displayEndTime
Label : Whether to display the endTime
Comment : A property that defines whether the end time of an event should be displayed.
Domain : displayEndTime
Range : Boolean
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/displayEndTime/#examples","title":"Examples","text":"Example usage of displayEndTime
json { \"@context\": [ \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\" ], \"type\": \"Event\", \"displayEndTime\": false }
URI : https://w3id.org/fep/8a8e/eventStatus
Label : The events status is
Comment : An eventStatus of an event represents its status; particularly useful when an event is cancelled or rescheduled.
Domain : Event
Range : EventStatusType (@vocab)
Required : No
Functional : Yes
See also : EventCancelled | EventScheduled | EventTentative | EventMovedOnline | EventPostponed | EventRescheduled
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/eventStatus/#examples","title":"Examples","text":"Example of a forward chronological OrderedCollection with additional context
{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"id\": \"https://domain.example/events/0\",\n \"type\": \"Event\",\n \"eventStatus\": \"EventScheduled\"\n}\n"},{"location":"fep/8a8e/externalParticipationUrl/","title":"externalParticipationUrl","text":"URI : https://w3id.org/fep/8a8e/externalParticipationUrl
rdfs:label : External participation URL
rdfs:comment : A URL that points to an external platform where people can join the event or where they can buy tickets for the event. Required if joinMode is set to external.
rdfs:domain : as:Event
rdfs:range : xsd:anyURI
rdfs:isDefinedBy : FEP\u20118a8e
"},{"location":"fep/8a8e/externalParticipationUrl/#examples","title":"Examples","text":"Example usage of externalParticipationUrl
json{ \"@context\": [ \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\" ], \"type\": \"Event\", \"url\": \"http://example.org/events/1234\", \"joinMode\": \"external\", \"externalParticipationUrl\": \"https://www.meetup.com/somegroup/events/00000/\" }
URI : https://w3id.org/fep/8a8e/isBannerImage
rdfs:label : Image is a banner image
rdfs:comment : Whether an image is an (events) banner image.
rdfs:domain : as:Image
rdfs:range : Boolean
rdfs:isDefinedBy : FEP\u20118a8e
"},{"location":"fep/8a8e/isBannerImage/#examples","title":"Examples","text":"Example usage of isBannerImage
json{ \"@context\": [ \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\", { \"toot\": \"http://joinmastodon.org/ns#\", \"focalPoint\": { \"@container\": \"@list\", \"@id\": \"toot:focalPoint\" } } ], \"type\": \"Event\", \"id\": \"https://example.org/new-year-party\", \"name\": \"New years party\", \"organizers\": null, \"startTime\": \"2014-12-31T23:00:00-08:00\", \"endTime\": \"2015-01-01T04:00:00-08:00\", \"image\": { \"type\": \"Image\", \"mediaType\": \"image/jpeg\", \"url\": \"https://example.com/images/new-year-party-flyer.png\", \"focalPoint\": [ -0.55, 0.43 ] }, \"attachment\": [ { \"type\": \"Image\", \"mediaType\": \"image/jpeg\", \"url\": \"https://example.com/images/new-year-party-banner.png\", \"width\": 1000, \"height\": 500, \"isBannerImage\": true } ] }
URI : https://w3id.org/fep/8a8e/joinMode
rdfs:label : Join mode
rdfs:comment : Indicator of how new members may be able to join an event. Accepted values: free, restricted, external, none, invite. If external, you must also set externalParticipationUrl.
rdfs:domain : as:Event
rdfs:range : A string
rdfs:isDefinedBy : FEP\u20118a8e
"},{"location":"fep/8a8e/joinMode/#examples","title":"Examples","text":"Example: restricted
```json{ \"@context\": [ \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\" ], \"type\": \"Event\", \"url\": \"http://example.org/events/1234\", \"joinMode\": \"restricted\" }
Example: external\n\n```json{\n \"@context\": [\n \"https://w3id.org/fep/8a8e\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": \"Event\",\n \"url\": \"http://example.org/events/1234\",\n \"joinMode\": \"external\",\n \"externalParticipationUrl\": \"https://www.escample.org/events/1234/participate\"\n}\n"},{"location":"fep/8a8e/organizers/","title":"organizers","text":"URI : https://w3id.org/fep/8a8e/organizers
Label : Organizers Collection
Comment : An ActivityStreams Collection collection that lists the entities that are disclosed to as organizers of an event. If set to null this explicitly indicates a deliberate choice to withhold organizer information.
Range : OrganizersCollection (SubType of of the Collection or OrderedCollection ActivityStreams Type) or null
Required : Yes
Functional : No
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/organizers/#examples","title":"Examples","text":"Example usage of organizers
json { \"@context\": [ \"https://schema.org\", \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\", { \"sc\": \"http://schema.org/\" } ], \"type\": \"Event\", \"organizers\": { \"type\": \"OrganizersCollection\", \"totalItems\": 4, \"items\": [ { \"type\": \"Person\", \"name\": \"ActivityPub Person Actor\", \"id\": \"https://example.org/actors/1\"}, { \"type\": \"Link\", \"href\": \"https://organizer1.example.org\"}, { \"type\": \"sc:Person\", \"name\": \"Alice\" }, { \"type\": \"sc:Organization\", \"name\": \"Event Co.\" } ] } }
URI : https://w3id.org/fep/8a8e/previousStartTime
Label : The event previous start time.
Comment : The events previous start time is the old start time before an event got postponed or rescheduled.
Domain : Event
Range : xsd:dateTime (@vocab)
See also : EventPostponed | EventRescheduled
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/requiredJoinVisibility/","title":"requiredJoinVisibility","text":"Label : Required Join Visibility
Comment : Specifies the minimum audience that must be addressed in a valid Join activity related to the event. This can include individual actors, groups, the as:Public collection, or any other URI. The Join activity must be addressed accordingly (e.g., using to, cc, bto, or audience).
Domain : as:Event
Range : A list of any addressed targets, i.e. URIs (@list)
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/requiredJoinVisibility/#examples","title":"Examples","text":"Example usage of requiredJoinVisibility
json{ \"@context\": [ \"https://schema.org\", \"https://https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\" ], \"type\": \"Event\", \"id\": \"https://example.org/yoga-workshop\", \"name\": \"Yoga Workshop with Alice and Bob\", \"startTime\": \"2014-12-12T18:00:00-08:00\", \"endTime\": \"2014-12-12T19:30:00-08:00\", \"attributedTo\": \"https://example.org/groups/fediyoga\", \"organizers\": { \"type\": \"OrganizersCollection\", \"id\": \"https://example.org/yoga-workshop/organizers\", \"totalItems\": 3, \"first\": { \"type\": \"CollectionPage\", \"partOf\": \"https://example.org/yoga-workshop/organizers\", \"items\": [ \"https://example.org/users/bob\", \"https://example.org/users/alice\", \"https://example.org/groups/fediyoga\" ] }, \"joinMode\": \"restricted\", \"requiredJoinVisibility\": [ \"https://example.org/yoga-workshop/organizers\" ] }
URI : https://w3id.org/fep/8a8e/timezone
Label : The timezone of an Event
Comment : Indicates the timezone for which the time(s) indicated in the event are given. The value provided should be among those listed in the IANA Time Zone Database.
Is defined by : FEP-8a8e
Range : IANA Time Zone identifier
Required : No
Functional : No
"},{"location":"fep/8a8e/timezone/#examples","title":"Examples","text":"Example usage of timezone
json { \"@context\": [ \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\" ], \"type\": \"Event\", \"id\": \"https://example.org/events/new-years-party\", \"name\": \"New years party\", \"startTime\": \"2014-12-31T23:00:00Z\", \"endTime\": \"2015-01-01T06:00:00Z\", \"timezone\": \"Europe/Vienna\", \"organizers\": null }
URI : https://w3id.org/fep/8a8e/upcomingEvents
Label : Ordered collection of upcoming Events
Comment : An ActivityStreams OrderedCollection of Event objects that have a startTime property in the future, sorted by startTime with the earliest first.
Range : An OrderedCollection (@id)
Is defined by : FEP-8a8e
"},{"location":"fep/8a8e/upcomingEvents/#examples","title":"Examples","text":"Example usage of upcomingEvents
json { \"@context\": [ \"https://w3id.org/fep/8a8e\", \"https://www.w3.org/ns/activitystreams\" ], \"type\": \"Organization\", \"upcomingEvents\": { \"type\": \"OrderedCollection\", \"items\": [ { \"type\": \"Event\", \"startTime\": \"2025-06-01T12:00:00Z\" }, { \"type\": \"Event\", \"startTime\": \"2025-07-15T15:30:00Z\" } ] } }
DRAFT implementation 2022-11-12 #29 Discussions codeberg"},{"location":"fep/8b32/#summary","title":"Summary","text":"This proposal describes how ActivityPub servers and clients could create self-authenticating activities and objects.
HTTP signatures are often used for authentication during server-to-server interactions. However, this ties authentication to activity delivery, and limits the flexibility of the protocol.
Integrity proofs are sets of attributes that represent digital signatures and parameters required to verify them. These proofs can be added to any activity or object, allowing recipients to verify the identity of the actor and integrity of the data. That decouples authentication from the transport, and enables various protocol improvements such as activity relaying, embedded objects and client-side signing.
"},{"location":"fep/8b32/#history","title":"History","text":"Mastodon supports Linked Data signatures since 2017, and a number of other platforms added support for them later. These signatures are similar to integrity proofs, but are based on outdated Linked Data Signatures 1.0 specification, which has been superseded by other standards.
"},{"location":"fep/8b32/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/8b32/#integrity-proofs","title":"Integrity proofs","text":"The proposed authentication mechanism is based on Data Integrity specification.
"},{"location":"fep/8b32/#proof-generation","title":"Proof generation","text":"The proof MUST be created according to the Data Integrity specification, section 4.2 Add Proof.
The process of proof generation consists of the following steps:
The resulting proof is added to the original JSON object under the key proof. Objects SHOULD NOT contain more than one integrity proof.
The list of attributes used in integrity proof is defined in Data Integrity specification, section 2.1 Proofs. The proof type SHOULD be DataIntegrityProof, as specified in section 3.1 DataIntegrityProof. The value of proofPurpose attribute MUST be assertionMethod.
The value of the verificationMethod attribute of the proof can be an HTTP(S) URI or a DID URL.
The controlled identifier document where the verification method is expressed MUST be an actor object or a DID document that is provably associated with an ActivityPub actor (e.g. using a mechanism described in FEP-c390 or FEP-ef61). The verification method MUST be associated with the assertionMethod property of the controlled identifier document. If controlled identifier document is an actor object, implementers SHOULD use assertionMethod property as described in FEP-521a.
Recipients of an object SHOULD perform proof verification if it contains integrity proofs. Verification process MUST follow the Data Integrity specification, section 4.4 Verify Proof. It starts with the removal of the proof value from the JSON object. Then verification method is retrieved from the controlled identifier document as described in Controlled Identifiers specification, section 3.3 Retrieve Verification Method. Then the object is canonicalized, hashed and signature verification is performed according to the parameters specified in the proof.
The subject of the controlled identifier document where the verification method is expressed MUST be the owner of the signed object, or a DID that is provably associated with that actor (e.g. using a mechanism described in FEP-c390 or FEP-ef61).
If both HTTP signature and integrity proof are used, the integrity proof MUST be given precedence over HTTP signature. The HTTP signature MAY be dismissed.
"},{"location":"fep/8b32/#algorithms","title":"Algorithms","text":"Implementers are expected to pursue broad interoperability when choosing algorithms for integrity proofs.
eddsa-jcs-2022 cryptosuite is RECOMMENDED:
Integrity proofs and linked data signatures can be used together, as they rely on different properties (proof and signature, respectively).
If compatiblity with legacy systems is desired, the integrity proof MUST be created and inserted before the generation of the linked data signature.
If both proof and signature are present in a received object, the linked data signature MUST be removed before the verification of the integrity proof.
Implementers using integrity proofs as an authentication mechanism are advised to follow the recommendations given in FEP-fe34: Origin-based security model.
"},{"location":"fep/8b32/#privacy-considerations","title":"Privacy considerations","text":"If a private object is signed, its authenticity can be proven if it is distributed beyond the intended recipients. This risk can be mitigated by encrypting private content.
"},{"location":"fep/8b32/#examples","title":"Examples","text":""},{"location":"fep/8b32/#signed-object","title":"Signed object","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"id\": \"https://server.example/objects/1\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://server.example/users/alice\",\n \"content\": \"Hello world\",\n \"proof\": {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://server.example/users/alice#ed25519-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n}\n"},{"location":"fep/8b32/#signed-activity","title":"Signed activity","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"id\": \"https://server.example/activities/1\",\n \"type\": \"Create\",\n \"actor\": \"https://server.example/users/alice\",\n \"object\": {\n \"id\": \"https://server.example/objects/1\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://server.example/users/alice\",\n \"content\": \"Hello world\"\n },\n \"proof\": {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://server.example/users/alice#ed25519-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n}\n"},{"location":"fep/8b32/#signed-activity-with-embedded-signed-object","title":"Signed activity with embedded signed object","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"id\": \"https://server.example/activities/1\",\n \"type\": \"Create\",\n \"actor\": \"https://server.example/users/alice\",\n \"object\": {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"id\": \"https://server.example/objects/1\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://server.example/users/alice\",\n \"content\": \"Hello world\",\n \"proof\": {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://server.example/users/alice#ed25519-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n },\n \"proof\": {\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v2\"\n ],\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"verificationMethod\": \"https://server.example/users/alice#ed25519-key\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\",\n \"created\": \"2023-02-24T23:36:38Z\"\n }\n}\n"},{"location":"fep/8b32/#test-vectors","title":"Test vectors","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/8c3f/","title":"FEP-8c3f: Web Monetization","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Diogo Peralta Cordeiro mail@diogo.site, Phablulo Joel phablulo@gmail.comWITHDRAWN 2022-01-18 2023-10-27 #3 - codeberg"},{"location":"fep/8c3f/#summary","title":"Summary","text":"Web Monetization federation via [ActivityPub].
"},{"location":"fep/8c3f/#history","title":"History","text":"The ability to transfer money has been a long-standing omission from the web platform. As a result, the web suffers from a flood of advertising and corrupt business models. Web Monetization provides an open, native, efficient, and automatic way to compensate creators, pay for API calls, and support crucial web infrastructure.
[Web Monetization] is being proposed as a W3C standard at the Web Platform Incubator Community Group.
"},{"location":"fep/8c3f/#requirements","title":"Requirements","text":"In GNU social this is implemented on a plugin using an extra property gs:webmonetizationWallet on the actor object.
{\n \"type\": \"Person\",\n \"streams\": [],\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"gs\": \"https://www.gnu.org/software/social/ns#\"\n },\n {\n \"webmonetizationWallet\": {\n \"@id\": \"gs:webmonetizationWallet\",\n \"@type\": \"@id\"\n }\n }\n ],\n \"id\": \"https://social.gnusocial.rocks/actor/42\",\n \"inbox\": \"https://social.gnusocial.rocks/actor/42/inbox.json\",\n \"outbox\": \"https://social.gnusocial.rocks/actor/42/outbox.json\",\n \"following\": \"https://instance.gnusocial.test/actor/42/subscriptions\",\n \"followers\": \"https://instance.gnusocial.test/actor/42/subscribers\",\n \"preferredUsername\": \"alice\",\n \"name\": \"Alyssa P.Hacker\",\n \"url\": \"https://social.gnusocial.rocks/@alice\",\n \"webmonetizationWallet\": \"$wallet.example.com/alice\"\n}\n"},{"location":"fep/8c3f/#about-the-value-of-gswebmonetizationwallet","title":"About the value of gs:webmonetizationWallet","text":"That string is the same as the example one in Web Monetization specification and it consists on a payment pointer.
Payment Pointers start with a $ character to distinguish them from other identifiers and make it obvious that they are related to payments. To convert a Payment Pointer to a URL the $ is replaced with the standard prefix of a secure URL, https://.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/8fcf/","title":"FEP-8fcf: Followers collection synchronization across servers","text":"Authors Status Date received Date final Tracking issue Discussions Repository Claire claire.fep-1d7d@sitedethib.comFINAL 2020-10-24 2022-02-07 #6 - codeberg"},{"location":"fep/8fcf/#summary","title":"Summary","text":"In ActivityPub, follow relationships are established, updated and removed by sending activities such as Follow, Accept or Reject, which are assumed to be correctly and promptly processed upon receipt.
However, due to incompatible protocol extensions, software bugs, server crashes or database rollbacks, the two ends of a Follow relationship may end up out of sync.
This can be especially damaging when a remote instance has outdated information about follow relationships that should have been revoked, as some implementations may deliver activities addressed to the sender's followers collection by using the sharedInbox mechanism and letting the recipient use the sender's followers collection for local delivery and access control.
This proposal describes an optional mechanism for detecting discrepancies in following relationships across instances, with minimal overhead and without loss of privacy.
"},{"location":"fep/8fcf/#requirements","title":"Requirements","text":"The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].
The proposed protocol for followers collection synchronization makes a number of assumptions that may not be suitable to every implementation or deployment.
Implementations and deployments MUST NOT implement the mechanisms described in this proposal unless they match the following requirements: - actors managed by an instance are required to all share the same exact URI scheme and authority for their id, inbox and sharedInbox URIs - such instances are required to manage all actors using the same URI scheme and authority for either their id, inbox or sharedInbox URIs (that is, for instance, two fediverse implementations cannot implement this proposal if they are set up on the same exact same domain name, unless implementing an additional mechanism to share follower information between them, which is out of scope for this proposal).
The reason for those requirements is to prevent the partial followers collection described below from missing legitimate followers, which could result in followers being removed for no reason.
Failing to implement this proposed synchronization mechanism should not impact compatibility with other implementations, as it is completely optional.
"},{"location":"fep/8fcf/#partial-follower-collection","title":"Partial follower collection","text":"For efficiency and privacy purposes, we consider a subset of an actor's followers collection. This subset is the set of an actor's followers whose id shares an instance's specific URI scheme and authority.
For instance, if https://example.org/users/1 has the following followers: - https://example.org/users/2 - https://testing.example.org/users/1 - https://next.example.org/users/foo - https://testing.example.org/users/2
The partial follower collection of https://example.org/users/1 for the instance serving https://testing.example.org/users/1 is: - https://testing.example.org/users/1 - https://testing.example.org/users/2
To enable quick checking of partial followers consistency across instances, a partial follower collection digest is computed.
This digest is created by XORing together the individual SHA256 digests of each follower's id.
partialCollectionDigest = SHA256(follower1) XOR SHA256(follower2) XOR ... XOR SHA256(followerN)\n For instance, the partial follower collection digest of https://example.org/users/1 for the instance serving https://testing.example.org/users/1 is: 3a06e99569547f444c352ab7f52e4bab207abec5ca6f07b0045cfdc9723f8fa9 XOR f939a1585d4a8f02ee339210dbe7315d7003476663d6095f7d996fc4bc7a49b6 = c33f48cd341ef046a206b8a72ec97af65079f9a3a9b90eef79c5920dce45c61f
Collection-Synchronization HTTP Header","text":"The Collection-Synchronization HTTP header provides a mechanism for quickly checking whether the sender's followers collection part that is relevant to the recipient is consistent with the recipient's knowledge.
The header field name is Collection-Synchronization and its value is a list of parameters and values, formatted according to the signature syntax defined in [HTTP-Signatures], Section 4.1.
Example:
Collection-Synchronization: collectionId=\"https://example.org/users/1/followers\", url=\"https://example.org/users/1/followers_synchronization\", digest=\"c33f48cd341ef046a206b8a72ec97af65079f9a3a9b90eef79c5920dce45c61f\"\n"},{"location":"fep/8fcf/#collection-synchronization-header-parameters","title":"Collection Synchronization Header Parameters","text":"The Collection-Synchronization header's parameters are defined as follows:
collectionId: this is URI of the collection that supports synchronization. It must be the sender's followers collection.url: this is the URL of the partial followers collection intended for the receiving instance. Accessing it should require authentication from the receiving instance.digest: the partial follower collection digest intended for the receiving instance.When delivering an Activity to an inbox (or sharedInbox), an instance MAY set a Collection-Synchronization header intended for the corresponding instance (determined by the inbox URI scheme and authority).
When exactly to set this header is up to the sender, but it is recommended to at least send it for any Create activity addressed specifically to the sender's followers collection.
On the receiving end, upon receiving an Activity delivery with a signed Collection-Synchronization header, the receiver MUST check that: - the collectionId attribute matches the sender's followers collection id - the url attribute also matches the same authority (so that the instance cannot get tricked into requesting the followers list of a third-party individual)
If any of those checks fails, the receiver MUST ignore the Collection-Synchronization header.
The receiver SHOULD then compute the partial collection digest for the sender's followers based on its own knowledge. If the digest does not match the digest attribute of the header, it SHOULD then query the url, authenticating itself to the remote server using [HTTP-Signatures] or another method.
Having fetched the up-to-date partial followers collection from the autoritative server, the receiving end: - SHOULD remove from its local copy of the followers collection any local actor not listed in the partial followers collection. - MAY consider any pending outgoing follow listed in the partial followers collection as accepted. - SHOULD send an Undo Follow for any other local follower listed in the partial followers collection but not known locally.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/9091/","title":"Index","text":"hide: - navigation description: \"This FEP defines an API endpoint used to initiate the \"Export Actor\" operation. The output and semantics of the result of the export operation is out of scope, and left to subsequent FEPs. The endpoint only specifies how to start the operation, and by extension, how to tell if a given Actor's server supports this operation.\"
"},{"location":"fep/9091/#fep-9091-export-actor-service-endpoint","title":"FEP-9091: Export Actor Service Endpoint","text":"Authors Status Date received Tracking issue Discussions Repository Dmitri Zagidulin @dmitri@social.coopDRAFT 2024-07-08 #358 Discussions codeberg"},{"location":"fep/9091/#summary","title":"Summary","text":"This FEP defines an API endpoint used to initiate the \"Export Actor\" operation. The output and semantics of the result of the export operation is out of scope, and left to subsequent FEPs. The endpoint only specifies how to start the operation, and by extension, how to tell if a given Actor's server supports this operation.
"},{"location":"fep/9091/#data-model-export-service-endpoint","title":"Data Model: Export Service Endpoint","text":"Conforming ActivityPub Actor profiles MUST specify their export API endpoints using the service descriptor property (inspired by the DID Core Services section, and used in other FEPs such as FEP-e3e9: Actor Relative URLs):
type of the service descriptor object MUST be https://w3id.org/fep/9091#Export (see example below).service property MUST NOT contain more than one node with the #Export type.#Export, the client SHOULD just use the first entry.{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/did/v1\"\n ],\n \"id\": \"https://alice-personal-site.example/actor\",\n \"type\": \"Person\",\n \"name\": \"Alice\",\n \"service\": [{\n \"id\": \"https://alice-personal-site.example/actor#export\",\n \"type\": \"https://w3id.org/fep/9091#Export\",\n \"serviceEndpoint\": \"https://alice-personal-site.example/actor/accountExport\"\n }]\n}\n"},{"location":"fep/9091/#protocol-initiating-the-export-actor-operation","title":"Protocol: Initiating the Export Actor Operation","text":"If a client conforming to this FEP wishes to start the 'Export Actor' process, such as to create a backup of the Actor profile, its private key material, and all content associated with this actor, the client MUST perform the following steps:
service description object with the expanded type equal to https://w3id.org/fep/9091#Export.Locate the corresponding serviceEndpoint URL, in that description object.
The client makes an HTTP POST to the URL in the serviceEndpoint to initiate the export operation.
Example synchronous request (with an empty body) and response:
POST /actor/accountExport HTTP/1.1\nHost: alice-personal-site.example\nAuthorization: Bearer ....\nAccept: application/x-tar\nContent-Length: 0\n HTTP/1.1 200 OK\nContent-type: application/x-tar\nTransfer-Encoding: chunked\n\n<binary data of the resulting .tar file>\n"},{"location":"fep/9091/#references","title":"References","text":"FEP-e3e9: Actor Relative URLs
Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/9098/","title":"FEP-9098: Custom emojis","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2025-07-06 #648 Discussions codeberg"},{"location":"fep/9098/#summary","title":"Summary","text":"A custom emoji is a small image used to express an idea or emotion. Custom emojis are different from Unicode emojis, which are sequences of characters.
This document describes how custom emojis are implemented in the ActivityPub network.
"},{"location":"fep/9098/#history","title":"History","text":"Custom emojis were introduced by Pleroma (commits) and Mastodon (PR) in 2017.
"},{"location":"fep/9098/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/9098/#emoji-object","title":"Emoji object","text":"Custom emojis are represented as Emoji objects (the full type IRI is http://joinmastodon.org/ns#Emoji). The Emoji object has the following properties:
id (RECOMMENDED): the unique identifier of the custom emoji. Some implementations omit this property (i.e. the object is anonymous).type (REQUIRED): the literal string Emoji.name (REQUIRED): the shortcode of the custom emoji (the name of the emoji, enclosed in colons; see Microsyntax section).updated (OPTIONAL): the date when custom emoji was updated, formatted as RFC-3339 date and time string.icon (REQUIRED): the Image object describing the emoji image.type (REQUIRED): the literal string Image.url (REQUIRED): the URI of the image to use as the emoji.Note
The definition of the icon property in ActivityStreams Vocabulary recommends an aspect ratio of one to one, but in practice custom emojis have varied aspect ratios.
Example:
{\n \"id\": \"https://social.example/emoji/blobcat\",\n \"type\": \"Emoji\",\n \"name\": \":blobcat:\",\n \"updated\": \"1970-01-01T00:00:00Z\",\n \"icon\": {\n \"type\": \"Image\",\n \"url\": \"https://social.example/media/blobcat.png\"\n }\n}\n"},{"location":"fep/9098/#uniqueness","title":"Uniqueness","text":"The primary unique identifier of a custom emoji is a combination of its name and the domain name. The domain name can be extracted from the id of Emoji object or from the id of the object within which it is embedded.
If a publisher doesn't guarantee that emoji id is globally unique, it MUST NOT add this property.
The short description of a custom emoji can be specified with the alternateName property (the full IRI is http://schema.org/alternateName).
In text a custom emoji is represented by its shortcode, which is a name of an emoji enclosed in colons (e.g. :blobcat:). This is also the value of the Emoji object's name property.
These textual representations are often inserted into values of name, summary and content properties of the object that embeds the custom emoji. E.g., the content of a Note, or the name of an Actor.
The corresponding Emoji objects are added to the tag array of the object. Within that array custom emojis can appear in any order.
Example:
{\n \"type\": \"Note\",\n \"id\": \"https://social.example/notes/1234\",\n \"content\": \"<p>:blobcat:</p>\",\n \"tag\": [\n {\n \"id\": \"https://social.example/emoji/blobcat\",\n \"type\": \"Emoji\",\n \"name\": \":blobcat:\",\n \"icon\": {\n \"type\": \"Image\",\n \"url\": \"https://social.example/media/blobcat.png\"\n }\n }\n ]\n}\n"},{"location":"fep/9098/#right-to-left-text","title":"Right-to-left text","text":"Custom emoji shortcodes are always written left-to-right, even in right-to-left text.
"},{"location":"fep/9098/#rendering","title":"Rendering","text":""},{"location":"fep/9098/#general-client-considerations","title":"General client considerations","text":"The aspect ratio of an image SHOULD be preserved. The maximum width of an image MAY be limited.
Shortcodes inside <code> and <pre> HTML elements MUST NOT be replaced.
Web applications typically replace custom emoji shortcodes with <img> HTML tags and render the output as HTML.
To prevent Cross-Site Scripting (XSS) attacks, implementers MUST ensure that:
&<>\"').summary, content) is processed, shortcodes are replaced only inside Text nodes.In order to be compatible with as many servers as possible, implementations SHOULD comply with the following additional requirements:
[a-zA-Z0-9_] set (Mastodon).image/png, image/gif or image/webp (Mastodon).This document is based on implementations of custom emojis in Pleroma, Mastodon, Misskey and Fedibird.
"},{"location":"fep/9098/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/96ff/","title":"Index","text":"hide: - navigation description: \"A number of vulnerabilities have occurred in ActivityPub implementations due to \"type confusion\" attacks - where unrelated files on the same hostnmae as an ActivityPub implementation are processed as obejcts with ActivityPub semantics. Such attacks have been mitigated by carefuly validating the Content-Type header (and by implementations ensuring that users cannot create files with the application/activity+json or application/ld+json content types), but it would bolster such defences if messages intended to be processed with ActivityPub semantics Additionally, ActivityPub nominally supports transfer syntaxes other than JSON-LD (such as any other RDF syntax like Turtle; or potentially a more bandwidth efficient syntax such as a hypothetical CBOR-LD). Strict content type filtering permanently prevents usage of such syntaxes in the future The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \" SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC2119. \"
"},{"location":"fep/96ff/#fep-96ff-explicit-signalling-of-activitypub-semantics","title":"FEP-96ff: Explicit signalling of ActivityPub Semantics","text":"Authors Status Date received Tracking issue Discussions Repository Erin Shepherd fep@erinshepherd.netDRAFT 2024-02-17 #272 - codeberg"},{"location":"fep/96ff/#summary","title":"Summary","text":"A number of vulnerabilities have occurred in ActivityPub implementations due to \"type confusion\" attacks - where unrelated files on the same hostnmae as an ActivityPub implementation are processed as obejcts with ActivityPub semantics.
Such attacks have been mitigated by carefuly validating the Content-Type header (and by implementations ensuring that users cannot create files with the application/activity+json or application/ld+json content types), but it would bolster such defences if messages intended to be processed with ActivityPub semantics
Additionally, ActivityPub nominally supports transfer syntaxes other than JSON-LD (such as any other RDF syntax like Turtle; or potentially a more bandwidth efficient syntax such as a hypothetical CBOR-LD). Strict content type filtering permanently prevents usage of such syntaxes in the future
The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \" SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC2119.
"},{"location":"fep/96ff/#signalling-of-activitypub-semantics","title":"Signalling of ActivityPub Semantics","text":"An implementation signifies its intention to use ActivityPub semantics for a request or response body by including a Link header with relation type type and contents of https://www.w3.org/TR/activitypub/. This header usage is borrowed from LDP, which uses the same relation type to indicate support of LDP semantics.
Link: <https://www.w3.org/TR/activitypub/>;rel=\"type\"\n A conformant implementation MUST include this link relation in any HTTP messages it intends to be processed with ActivityPub semantics.
A conformant implementation MUST process a HTTP message and where this link relation is present and the content type is an implementation supported transport syntax for ActivityStreams 2 with ActivityPub semantics.
A conformant implementation MAY process a HTTP message without any Link header with relation \"type\", where the Content-Type header contains exactly the value application/activity+json or application/ld+json with a profile parameter containing the token https://www.w3.org/ns/activitystreams with ActivityPub semantics (This enables backwards compatibility with existing implementations.)
A conformant implementation MUST NOT process any other HTTP messages with ActivityPub semantics.
Conformant implementations MUST support messages containing multiple Link headers, including multiple link headers with the \"type\" relation. The following examples are all valid and indicate that processing should occur with ActivityPub semantics:
Link: <https://www.w3.org/TR/activitypub/>;rel=\"type\"\n\nLink: <https://www.w3.org/TR/activitypub/>;rel=\"type\", <https://example.com/>;rel=\"test\"\n\nlink: <https://www.w3.org/TR/activitypub/>;rel=\"type\", <https://example.com/>;rel=\"type\"\n\nLINK: <https://www.w3.org/TR/activitypub/>;rel=\"type\"\nLink: <https://example.com/>;rel=\"test\"\n\nLink: <https://www.w3.org/TR/activitypub/>;rel=\"type\"\nlInK: <https://example.com/>;rel=\"type\"\n Implementations are encouraged to periodically add additional Link relations to their messages to ensure support by counterparties. (This process is typically termed greasing, after the GREASE extension to TLS)
For the avoidance of doubt, ActivityPub semantics apply only in cases where implementations expect to transfer ActivityStreams 2 documents with the semantics described in the ActivityPub specification. This link relation MUST NOT be included for other exchanges that an ActivityPub implementation may perform, including but not limited to WebFinger queries.
"},{"location":"fep/96ff/#why-not-just-rely-upon-the-content-type","title":"Why not just rely upon the content type?","text":"The content type alone does not specify semantics. The ActivityStreams 2 syntax can be used independently of ActivityPub, and non-ActivityPub systems such as Cohost produce ActivityStreams 2 documents.
Additionally, even though ActivityPub implementations can prevent the creation of unexpected resources with an ActivityStreams content type, they cannot prevent other applications running on the same name from permitting the untrusted creation of such resources.
It is hoped that a future version of this specification (after widespread deployment of explicit tagging of messages with explicit semantics) can omit the permission to determine semantics based upon media type.
"},{"location":"fep/96ff/#vulnerabilities-non-normative","title":"Vulnerabilities (Non-Normative)","text":"The following is a list of known implementation vulnerabilities which would have been prevented by implementation of this mechanism:
If all implmenetations strictly required implementation of the link relation check, the following vulnerabilities would also be prevented:
(This is not presently proposed because it would break compatibility with existing implementations)
These lists are likely not exhaustive.
"},{"location":"fep/96ff/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/9967/","title":"FEP-9967: Polls","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2025-01-23 #478 Discussions codeberg"},{"location":"fep/9967/#summary","title":"Summary","text":"How to make polls in ActivityPub network.
"},{"location":"fep/9967/#history","title":"History","text":"Mastodon implemented polls in 2019 (documentation).
ActivityStreams specification provides recommendations for representing questions, but these recommendations were found to be impractical and the de-facto standard differs from them.
"},{"location":"fep/9967/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/9967/#overview","title":"Overview","text":"sequenceDiagram\n actor Alice\n actor Bob\n actor Charlie\n Note right of Alice: Alice publishes a poll\n Alice ->> Bob: Create(Question)\n Alice ->> Charlie: Create(Question)\n Note left of Bob: Bob votes\n Bob ->> Alice: Create(Note)\n Note right of Alice: Alice receives the vote and updates poll results\n Alice ->> Bob: Update(Question)\n Alice ->> Charlie: Update(Question)"},{"location":"fep/9967/#question-object","title":"Question object","text":"A poll is represented by a Question object. It MUST be published with Create activity (in contrast with ActivityStreams vocabulary where Question itself is defined as intransitive activity).
A Question object is typically structured similarly to a Note. In addition to common properties such as attributedTo and content, it MUST have one of the following:
oneOf, if the poll requires choosing a single option.anyOf, if the poll allows choosing multiple options.The value of this property MUST be an array of objects representing poll options:
type: the Note string.name: the text of the poll option.replies: the total number of votes, as a collection:type: the Collection string.totalItems: the total number of votes for this poll option.The name of a poll option MUST be unique within a poll. If the text contains custom emoji microsyntax, the corresponding Emoji objects are added to the tag array of the Question object.
The number of poll options is often limited, but the limit differs between implementations.
Question objects MAY have an endTime property indicating the time when the poll ends. Some implementations use closed property instead of endTime or in addition to it (even if the poll is open). Consumers MUST process closed in the same way as endTime.
Question objects SHOULD have an updated property.
Example:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://social.example/polls/1\",\n \"type\": \"Question\",\n \"attributedTo\": \"https://social.example/actors/1\",\n \"to\": \"https://www.w3.org/ns/activitystreams#Public\",\n \"content\": \"<p>Question</p>\",\n \"endTime\": \"2024-07-17T18:18:17Z\",\n \"updated\": \"2024-07-16T20:53:05Z\",\n \"oneOf\": [\n {\n \"type\": \"Note\",\n \"name\": \"Answer 1\",\n \"replies\": {\n \"type\": \"Collection\",\n \"totalItems\": 596\n }\n },\n {\n \"type\": \"Note\",\n \"name\": \"Answer 2\",\n \"replies\": {\n \"type\": \"Collection\",\n \"totalItems\": 379\n }\n }\n ]\n}\n"},{"location":"fep/9967/#voting","title":"Voting","text":""},{"location":"fep/9967/#vote-object","title":"Vote object","text":"A vote is represented by a Note object with the following structure:
id: the ID of the vote.type: the Note string.attributedTo: the actor that is making this vote.inReplyTo: the ID of the Question object.name: the value of the name property of the chosen poll option.to: the author of the poll.The object MUST NOT have a content property.
This object is wrapped in a Create activity and sent to the author of the poll. If the poll is anonymous, this activity MUST NOT be delivered to anyone else.
When a poll allows multiple choices, each vote MUST be sent as a separate activity.
Example of a vote:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://social.example/votes/1\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://social.example/actors/2\",\n \"inReplyTo\": \"https://social.example/polls/1\",\n \"name\": \"Answer 1\",\n \"to\": \"https://social.example/actors/1\"\n}\n"},{"location":"fep/9967/#receiving-a-vote","title":"Receiving a vote","text":"The structure of a vote is very similar to a direct reply. It can be identified as a Note with name and inReplyTo properties, but without a content property.
Upon receving a vote, the author of the poll performs the following checks:
endTime is specified).id is not already registered.name is not already registered (if multiple choices are not allowed).If any of these checks fails, the vote MUST be ignored.
Otherwise, the author of the poll updates the vote count for the specified poll option (the totalItems property of a corresponding replies collection). The value of the updated property is changed to the current date and time.
When actor that published a poll receives a vote, it publishes an Update activity containing the Question object with updated results. This activity MUST be delivered to the audience of the poll and to every actor that voted in the poll.
The type of a poll (single choice / multiple choices) and its options might be changed at any time. In that case the author of the poll MUST reset the vote counts.
"},{"location":"fep/9967/#future-work","title":"Future work","text":"The method of identifying votes described in this document is not reliable because a reply can look exactly like a vote. Alternative representations are being discussed (e.g. the Respond activity).
This document is based on implementations of polls in Mastodon and Pleroma.
"},{"location":"fep/9967/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/9fde/","title":"FEP-9fde: Mechanism for servers to expose supported operations","text":"Authors Status Date received Tracking issue Discussions Repository Nik Clayton nik@ngo.org.ukDRAFT 2024-03-14 #282 - codeberg Version Date Changes 3.1 2024-03-13 - Convert to FEP format- Incorporate feedback from Johannes Ernst and Steve Bates- Renamed clientApis to operations so this is usable for server-server operations too- Added \"Open questions\" section 3.0 2024-01-22 Re-write; include the operation information in the nodeinfo 2.0 2023-09-25 Re-write; replace the original suggestion to use the OpenAPI definition with a simpler specification 1.0 2023-08-14 Initial draft"},{"location":"fep/9fde/#summary","title":"Summary","text":"This document proposes an extension to the NodeInfo schema (FEP-f1d5) that would allow developers of Mastodon and Mastodon-like servers to unambigiously communicate the operations their servers support, and allow developers of software that interoperates with those servers to detect those features, promoting interoperability and easier adoption of new features.
"},{"location":"fep/9fde/#synopsis","title":"Synopsis","text":"This document is written for:
After reading this document you should:
The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in RFC-2119.
"},{"location":"fep/9fde/#overview","title":"Overview","text":"[!IMPORTANT] Other servers are also clients
The primary perspective of this document's author is as an end-user client developer dealing with the Mastodon client API, and the examples are written from that perspective. However, this proposal can also be used to describe operations between different servers in the Fediverse using other protocols (e.g., ActivityPub). For any given interaction between a pair of servers one of them is going to be acting as the client in the interaction, and will need to know the operations the server supports.
For the purposes of this document a \"Mastodon or Mastodon-like\" server is a server that presents the Mastodon client API, optionally with extensions to that API that provide additional functionality. These servers include, but are not limited to:
Clients of these servers have an API discovery problem. Since different servers support different (but similar) APIs the client has to determine what API operations the server supports.
Given the wide variety of servers that are available, and their many forks, it's not feasible for clients to maintain an accurate list of all the possible server software names while mapping the names to API features.
Instead the server should have a mechanism for advertising the operations it supports.
The client would use this when determining what features to show the user, without needing to employ complex, error-prone heuristics.
This would also provide a clear mechanism for Mastodon and Mastodon-like servers to incrementally deploy new features and deprecate old ones without inconveniencing clients.
It also provides a clear mechanism to advertise server functionality without continually bolting it on to the \"instance info\" mechanism in the inconsistent fashion that has been done so far.
The rest of this document sets out the specific problems I'm interested in solving, with motivating examples, and then describes how the new approach would solve these problems.
"},{"location":"fep/9fde/#problems","title":"Problems","text":""},{"location":"fep/9fde/#the-supported-api-is-not-easily-discoverable","title":"The supported API is not easily discoverable","text":"Changes are made to the Mastodon API in a manner that is not easily discoverable by clients.
For example, Add POST /api/v1/conversations/:id/unread by ClearlyClaire \u00b7 Pull Request #25509 \u00b7 mastodon/mastodon \u00b7 GitHub adds a new API endpoint (api/v1/conversations/:id/unread).
The only way a client can discover that this API exists is to maintain, per-client, a mapping between Mastodon server version and the API supported at each version.
This is:
The Instance information contains a configuration block that has some, but not all the information necessary to determine the features a server supports.
Other servers have extended this information in incompatible ways (e.g., the pleroma block).
Other servers may not implement the functionality at all. For example, GoToSocial did not implement support for the Mastodon client-side filter API until version 0.15.0.
"},{"location":"fep/9fde/#no-standard-way-for-mastodon-like-servers-to-advertise-their-additional-functionality-to-clients","title":"No standard way for Mastodon-like servers to advertise their additional functionality to clients","text":"Mastodon-like servers implement some or all of the Mastodon API.
In many cases they also extend the API, providing additional functionality (local-only posting, quoting, markdown formatting, bookmarks, etc.)
In some cases that functionality has already been incorporated in Mastodon (e.g., bookmarks), in other cases there are plans to include that functionality in Mastodon (e.g., quoting, markdown formatting).
This leads to three problems.
Server developers already have a lot of work to do. Any proposal should therefore be straightforward to implement. Additional complexity, such as changing the contents of existing API responses, or requiring developers of different servers to tightly coordinate when new functionality is introduced is going to make it less likely that groups adopt any proposed solutions.
"},{"location":"fep/9fde/#proposed-solution","title":"Proposed solution","text":"A given Mastodon or Mastodon-like server supports a set of operations.
To expose those to the user a Mastodon client needs to know:
Therefore we need:
Operations are identified by the reverse FQDN of the server software that first implemented that operation, then an arbitrary number of additional dot-separated labels determined by the server authors.
This ensures that operation IDs are unique without needing tight coordination between different server developer groups.
For example:
org.joinmastodon.api.statuses.postorg.joinmastodon.api.statuses.translateio.github.glitch-soc.api.statuses.bookmarkdev.iceshrimp.api.notes.reactions.create[!NOTE] Precise reverse FQDN to use for each server is to be decided
This example use the reverse FQDNs for the server's primary websites or documentation sites, but each server group would determine and document the reverse FQDN for their server's operations.
[!NOTE] Dot-separated labels SHOULD map 1:1 to API endpoint components
In these examples the dotted components after the api correspond to the path components of the API endpoint. While there is no requirement that they do so server implementors are RECOMMENDED to do so, as it makes it easier for developers to mentally map between the operation identifier and the API path when they are working with the code.
Each label within the operation identifier is represented in lower-case US-ASCII (a character set containing 128 characters associated with code points in the range 0..7F) . If a server team wishes to define an operation that uses a non-US-ASCII label they MUST encode the operation label to US-ASCII using the ToASCII transformation described in RFC 3490 - Internationalizing Domain Names in Applications (IDNA).
Each operation exists at one or more semver-compatible (v2.0.0) versions. Semver is used because it is a widely deployed standard, easily understandable, and client libraries that can parse this format are available across many different programming languages.
For example, in the Mastodon API documentation \"Post a new status\" describes the API for posting a new status. That API has changed three times in the Mastodon server implementation.
scheduled_atpollThere are no backwards-incompatible breaking changes across those versions so this is the same operation at three different versions; per Semver the major version stays the same and the minor version is incremented.
1.0.0 - initial implementation1.1.0 - support for scheduled_at1.2.0 - support for polls[!IMPORTANT] These version numbers are unrelated to the version number of the software that introduced the operation
"},{"location":"fep/9fde/#example-bookmarks","title":"Example: Bookmarks","text":"Bookmarking statuses originated in the glitch-soc fork and was incorporated in to Mastodon.
Therefore, the ID for the bookmark operations -- if they are compatible with the glitch-soc implementation -- use the io.github.glitch-soc.api prefix.
io.github.glitch-soc.api.statuses.bookmark @ 1.0.0 - bookmark a statusio.github.glitch-soc.api.statuses.unbookmark @ 1.0.0 - remove a status from bookmarksio.github.glitch-soc.api.timeline.bookmarks @ 1.0.0 - fetch a timeline of the user's bookmarksio.github.glitch-soc.api.timeline.bookmarks @ 1.1.0 - fetch a timeline of the user's bookmarks, supporting min_id and max_id simultaneouslyClients must be able to discover which operations the server supports and the endpoints to use for those operations.
To do this the nodeinfo (determined via /.well-known/nodeinfo) schema should be extended to support a new operations property.
The property's value is a map from a string key -- the operation ID -- to a set of one or more Semver versions of the operation that the server supports.
For example:
\"operations\": {\n ...\n \"org.joinmastodon.api.some.operation\": [\"1.0.0\", \"1.1.0\", \"1.2.0\", \"2.0.0\"]\n ...\n}\n [!NOTE] Unordered versions
The supported version operations is not ordered; client code MUST treat this as a set, not a list.
[!NOTE] Not limited to Mastodon / Mastodon-like servers
This operations map is not limited to operations supported by Mastodon/Mastodon-like servers. This is a general mechanism that can be used by servers to expose information about their supported operations and could be used by other Fediverse software like Lemmy, KBin, etc.
Because of the Semver rules for breaking changes servers MAY omit earlier versions from the list if they are included in a later version. In the previous example the 1.0.0 and 1.1.0 versions can be omitted as a server supporting v1.2.0 of an operation implicitly supports all preceding versions with the same major number.
\"operations\": {\n ...\n \"org.joinmastodon.api.some.operation\": [\"1.2.0\", \"2.0.0\"]\n ...\n}\n [!NOTE] There is no need to specify the operation semantics
The semantics of each {operation, version} pair are already known by the client (for each operation it supports). Semantics like whether these endpoints are GET, POST, DELETE, or PATCH, the exact names of the URL query parameters, the API endpoint, etc.
In other words, servers MUST NOT advertise support for an existing operation ID and change anything about how that operation works. The server developers should either define and document a new operation ID, or implement the operation as a new version (bumping the major version if it is a breaking change).
"},{"location":"fep/9fde/#activitypub-extension-support","title":"ActivityPub extension support","text":"Most of this document has presented examples using the Mastodon client API (and related APIs) but, as noted, this proposal also provides a clean mechanism to allow servers to report other extensions they support.
Here is an example from FEP 6481 - # Specifying ActivityPub extension support with NodeInfo which is presented as:
Manyfold intends to define a new Activity Vocabulary Object type for its own rich content, 3dModel. The specification will define an extension IRI, for instance https://w3id.org/manyfold/3dModel#v1.
Applications that wish to receive Activities with this type can state their support for the extension in their NodeInfo file as follows; Manyfold instances will then know that they can send that object type and it will be understood.
{\n ...,\n \"metadata\": {\n \"activitypub\": {\n \"extensions\": [\n \"https://w3id.org/manyfold/3dModel#v1\"\n ]\n }\n }\n}\n In this proposal that could represented like this:
\"operations\": {\n \"app.manyfold.activitypub.accept.3dmodel\": [\"1.0.0\"],\n ...\n}\n (the precise name of the operation would need to be determined)
Any other server that can accept a 3dModel would expose the same operation.
Servers where the set of supported operations is not user configurable would need to maintain a static map of operations to versions, and return that map as part of the nodeinfo response.
If the set of operations is user configurable (e.g., perhaps the server software supports a translation API but the server operator has not enabled translation support) the nodeinfo response would need to be dynamically generated from the current software configuration.
In both cases developing a new operation or changing an existing operation would require the developers to:
To provide the best user experience client developers SHOULD fetch the operations map when the user logs in.
If the client supports a particular operation at a particular version the client can query the map and determine whether the concrete version they need is in the map, or met by a higher version. Semver client libraries are available for Kotlin and Java (Android) and Swift (iOS), as well as many other languages.
If the server does not support the operation the client MAY fall back to a different operation, or disable the particular operation in the UI.
To use the example from earlier, Add POST /api/v1/conversations/:id/unread by ClearlyClaire \u00b7 Pull Request #25509 \u00b7 mastodon/mastodon \u00b7 GitHub which adds a new API endpoint (api/v1/conversations/:id/unread).
The server would report this as:
\"operations\": {\n ...\n \"org.joinmastodon.api.conversations.id.unread\": [\"1.0.0\"]\n ...\n}\n and a client that wanted to conditionally support this would query the operations map for org.joinmastodon.api.conversations.id.unread with any version entry with a major version of 1, and if the operation/version pair is not found then disable the \"Mark a conversation unread\" UI affordances where they occur.
Yes.
I have started implementing the client side of this in Pachli. At the moment this uses server version parsing heuristics to maintain a Pachli-specific map of operations and supported versions (Server.kt ) and then query the server's reported capabilities and adjust the UI accordingly.
For example, this snippet conditionally enables the \"edit filters\" UI only if the user's server supports filtering.
Maintaining the server-specific operations map in Pachli is error prone, slow to update, and does not benefit the wider ecosystem of Mastodon clients and servers, hence this proposal.
"},{"location":"fep/9fde/#this-solves","title":"This solves...","text":"This solves the problems described earlier:
operations property must reflect the active configuration of the server.org.mastodon... operation identifier.This proposal doesn't address how clients can discover any limits associated with the operations. For example, how many characters are allowed per post, or the number of options that can be included in a poll.
That information is already included in the server's /api/v2/instance call (in the language of this proposal, the org.mastodon.api.instance operation).
I did consider extending the operations definition so that each operation mapped to an object that contained multiple keys, like this:
\"operations\": {\n \"org.joinmastodon.api.statuses.post\": {\n \"1.0.0\": {\n \"endpoint\": \"/api/v1/statuses\",\n \"limits\": {\n \"max_characters\": 500,\n // ...\n },\n \"mimeTypes\": [\"text/plain\"],\n // ...\n },\n \"1.1.0\": { /* ... */ }\n }\n}\n That would significantly complicate this proposal, increasing the risk that it's not adopted. There's also no clear value in doing this.
"},{"location":"fep/9fde/#alternatives-considered","title":"Alternatives considered","text":""},{"location":"fep/9fde/#reporting-capabilities-alongside-operations","title":"Reporting capabilities alongside operations","text":"It's tempting to think that operations could be broken down in to smaller parts.
For example, instead of different versions for the \"post a status\" operation you could include more specific capabilities in the operation description:
\"operations\": {\n ...\n \"org.joinmastodon.api.statuses.post\": {\n \"contentWarning\": true,\n \"polls\": true,\n \"media\": true,\n ...\n }\n ...\n}\n This indicates this server supports the \"post a new status\" operation with statuses that include content warnings, polls, and media.
You don't do that because it results in a combinatorial explosion of the different sub-types of operations that clients need to support, without any significant benefit.
Even the example above is incomplete; for example, some servers support including images in content warnings, so a simple boolean for the contentWarning property is insufficient.
So treating the thing-that-has-to-be-versioned as the operation (post a status, translate, reblog, etc) seems to be the better level of granularity.
"},{"location":"fep/9fde/#reporting-capabilities-in-api-responses","title":"Reporting capabilities in API responses","text":"A server could include metadata in each response that contains an object that describes the operations that can be performed on that object. For example, the Status object could be modified to include an operations property that looks like this:
{\n \"id\": \"103270115826048975\",\n \"created_at\": \"2019-12-08T03:48:33.901Z\",\n ...\n \"operations\": {\n \"org.joinmastodon.api.statuses.reply\": [\"POST\", \"https://example.com/api/v1/statuses\"],\n \"org.joinmastodon.api.statuses.view\": [\"GET\", \"https://example.com/api/v1/statuses/103270115826048975\"],\n \"org.joinmastodon.api.statuses.favourite\": [\"POST\", \"http/api/v1/statuses/103270115826048975/favourite\"],\n ... etc\n }\n }\n}\n This is the Hypermedia as the engine of application state (HATEOAS) model.
It's an interesting approach, and a possible future direction. But it would require significant work on the part of server developers to implement as it would affect every response returned by the server.
On the other hand the approach in this proposal is static content in the nodeinfo response. It's significantly easier to implement and iterate on.
"},{"location":"fep/9fde/#clients-keep-a-hardcoded-server-version-capabilities-map","title":"Clients keep a hardcoded server version : capabilities map","text":"This could go the other way, and instead require servers to have a consistent name and parseable version number, and expect clients to keep a map of \"server A at version V can perform operations X, Y, and Z\".
I think this is the wrong approach for two reasons:
First:
Re that last point a worked example might make it clearer.
Suppose there are two server types, A and B. A supports operations X and Y, B supports X, Y, and Z.
A client is released which supports operations X, Y, and Z, and is hardcoded with knowledge about which server type supports a given operation.
A new version of server type A is released which now supports operation Z as well. But users of the client who connect to server type A cannot benefit from this until a new version of the client is released with updated information about the capabilities of server type A.
With the proposal in this document this problem does not occur; if a client supports operation Z (at a given version) and a server advertises that it supports that operation then the client can choose to use it without needing a new release.
This is better for our users.
Second:
Server developers do not seem to be interested in reporting useful versions for their software, for example:
OpenAPI is a popular schema for defining an API. The server could just return the OpenAPI schema for the API that it supports.
I did consider this (an earlier version of this proposal was built around it). But it complicates the data the client needs to process, and includes data that the client will ignore.
Consider the /api/v1/timelines/home endpoint, which would have an operation ID something like org.joinmastodon.api.timelines.home under this proposal.
This is the OpenAPI definition for that endpoint, copied from the GoToSocial project's OpenAPI definition (swagger.yaml, the descriptions have been deleted to keep this a reasonable length):
/api/v1/timelines/home:\n get:\n description: |-\n The statuses [... deleted ...]\n operationId: homeTimeline\n parameters:\n - description: [deleted]\n in: query\n name: max_id\n type: string\n - description: [deleted]\n in: query\n name: since_id\n type: string\n - description: [deleted]\n in: query\n name: min_id\n type: string\n - default: 20\n description: [deleted]\n in: query\n name: limit\n type: integer\n produces:\n - application/json\n responses:\n \"200\":\n description: Array of statuses.\n headers:\n Link:\n description: [deleted]\n type: string\n schema:\n items:\n $ref: '#/definitions/status'\n type: array\n \"400\":\n description: bad request\n \"401\":\n description: unauthorized\n security:\n - OAuth2 Bearer:\n - read:statuses\n summary: See statuses/posts by accounts you follow.\n tags:\n - timelines\n Most of the information in that definition is redundant for the client.
It's absolutely essential information to have for the server developer, and for producing documentation.
But the client should already have this compiled in. The contract between the client and the server is that if the server reports that it supports the org.joinmastodon.api.timelines.home operation at v1.0.0:
/api/v1/timelines/homemax_id, since_id, min_id, and limit.StatusLink headerSo returning an OpenAPI definition to the client significantly complicates things for no benefit.
OpenAPI is also endpoint-oriented; by which I mean that the definition leads with the endpoint (/api/v1/statuses) and then describes the single operation that is present at that endpoint.
This is backwards to what we need, where the operation comes first, and multiple operations might be supported at the same endpoint.
"},{"location":"fep/9fde/#use-the-api-path-as-the-key","title":"Use the API path as the key","text":"Instead of the proposed format, use the API path as the map key, like this:
\"operations\": {\n \"/api/some/path\": [\"org.joinmastodon.x.y.z:1.0.0\", \"org.joinmastodon.x.y.z:1.1.0\", ...]\n}\n I reject this for two reasons:
These are questions this proposal does not explicitly answer. I have suggestions, but feedback is appreciated.
"},{"location":"fep/9fde/#can-servers-set-an-api-prefix","title":"Can servers set an API prefix?","text":"While I do not believe servers should be able to arbitrarily change all aspects of the path an operation is served under it may be useful for servers to be able to specify a path prefix for some or all operations.
E.g., a server that supports Mastodon and Friendica APIs might want to offer them at paths that starts mastodon and friendica respectively.
To do this the map value would be a second object with prefix and versions keys.
\"operations\": {\n \"org.joinmastodon.api.conversations.id.unread\": {\n \"prefix\": \"/mastodon\",\n \"versions\": [\"1.0.0\"]\n },\n \"ca.friendi.api.conversations.id.unread\": {\n \"prefix\": \"/friendica\",\n \"versions\": [\"1.0.0\"]\n }\n}\n If the prefix was omitted the default would be /.
Adopting this format, even if the only key is versions is probably a good idea initially to support future changes in a backwards-compatible manner.
If a FQDN owner entity rebrands or disappears (e.g., Calckey renamed to Firefish) what happens to the operation identifiers they have defined?
I don't think a rebrand should necessitate a wholesale renaming of the operation identifiers, that's unnecessary churn. They may decide to define future operations using the new name.
Since the operation identifies are not actual domains / URLs there is no requirement for them to actually resolve to anything, so I don't foresee a technical problem with this approach if a project that defined some operation identifiers shuts down. As long as there is still documentation that defines the API for those operations nothing is lost.
"},{"location":"fep/9fde/#is-the-reverse-fqdn-with-suffix-approach-the-best-format-for-operations-keys","title":"Is the reverse-FQDN-with-suffix approach the best format for operations keys?","text":"I think so.
I did consider using Uniform Resource Name (URN) or Internationalised Resource Identifiers (IRI) . There are advantages; for example, the operation identifier could be a URN or IRI that resolves to a document (perhaps the OpenAPI definition) for the API for that operation.
However, that introduces the difficult topic of case-sensitivity when clients are comparing the list of operations the server returns with the list of operations they support.
If the operation key was a URN or IRI it is, by definition, case-sensitive in parts. This provides an opportunity for server developers to incorrectly report an identifier with the wrong case, and for client developers to look for operations with the wrong case, impeding interoperability.
Suggesting \"Clients should case-fold operation identifiers to lower case before comparing them\" does not solve the problem, as rules for correctly lower-casing text are not well specified, and may differ from implementation to implementation, again impeding interoperability.
The approach of using a reverse FQDN with additional labels, and ensuring it is encoded with IDN sidesteps those problems.
"},{"location":"fep/9fde/#is-there-a-11-mapping-between-operations-and-api-calls","title":"Is there a 1:1 mapping between operations and API calls?","text":"Should every supported API call have an associated operation, or is it OK to consider multiple API calls as a single operation the server supports?
I think a 1:1 mapping makes most sense, as it allows server developers to incrementally deploy new features.
For example, at the time of writing Friendica only partially supports voting; posts with votes can be displayed and will be returned by the API, but the operations to create a post with a poll, or to vote on a poll are not implemented.
With a 1:1 mapping beween API and operations a Friendica server can advertise \"I can return posts that can contain polls; I do not support API calls that post polls or vote on them\".
If \"voting\" was treated as a single operation then Friendica would be forced to advertise \"I do not support polls\", and a client might unnecessarily not show polls attached to Friendica posts.
Anything other than a 1:1 mapping could also lead to different server developer teams \"bundling\" API calls into single operations in different ways, making things more complex for client developers, not less.
"},{"location":"fep/9fde/#should-clients-indicate-the-operation-identifier-when-making-a-request","title":"Should clients indicate the operation identifier when making a request?","text":"When a server supports multiple versions of an API at a single endpoint it can be challenging to reliably determine the version of the API the client is calling. Server software often has to resort to \"sniffing\" the request and deducing the client's intentions by the presence or absence of specific properties in the request.
This makes it more difficult to write the code to process the request and to determine what error details should be returned to the client.
I think trying to solve this problem is outside the scope of this proposal. However, if this proposal is adopted I recommend server developers include the operation ID as a mandatory specific property in all future changes to their API, so this problem is gradually solved.
"},{"location":"fep/9fde/#does-this-need-to-be-a-new-top-level-key-in-the-nodeinfo","title":"Does this need to be a new top-level key in the nodeinfo?","text":"No. This could also be in the metadata, exposed as:
{\n \"version\": \"2.2\",\n ...\n \"metadata\": {\n \"operations\": {\n // operation data here\n }\n }\n}\n"},{"location":"fep/9fde/#related-links-prior-art","title":"Related links / prior art","text":"Not an exhaustive list:
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/a070/","title":"Index","text":"hide: - navigation description: \"In a [Github-issue] filed against the normative AS2 context, it was pointed out that attachment and tag are unordered by default, although some implementations of \"fediverse\" software blindly assume them to always be ordered. This can be made unambiguous by using @list in JSON-LD, but for plain JSON consumers, a separate shorthand term must be defined. This FEP attempts to disambiguate between unordered and ordered arrays for those plain JSON consumers.\"
"},{"location":"fep/a070/#fep-a070-ordered-properties-for-plain-json-consumers","title":"FEP-a070: Ordered properties for plain JSON consumers","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT 2023-06-13 #121 - codeberg"},{"location":"fep/a070/#summary","title":"Summary","text":"In a [Github-issue] filed against the normative AS2 context, it was pointed out that attachment and tag are unordered by default, although some implementations of \"fediverse\" software blindly assume them to always be ordered. This can be made unambiguous by using @list in JSON-LD, but for plain JSON consumers, a separate shorthand term must be defined. This FEP attempts to disambiguate between unordered and ordered arrays for those plain JSON consumers.
items vs orderedItems","text":"(This section is non-normative.)
In the [AS2-vocab], there is items, which is used to express the items included in a Collection or OrderedCollection. However, in [ActivityPub], the OrderedCollection type is mandated to be reverse chronological order. Tangentially, it is valid to have a Collection with items that are either ordered or unordered.
The normative [AS2-context] deals with issue by defining two different terms: items and orderedItems. Both of these terms have the same @id of https://www.w3.org/ns/activitystreams#items and the same @type of @id to indicate that they contain nodes on the graph, but orderedItems is additionally defined with a @container of an ordered @list, overriding the default @container of an unordered @set.
The use of orderedItems is present in the examples for items within [AS2-vocab], but it is not defined separately because it is not a separate term. Following this pattern, we can define similarly \"ordered\" counterparts to \"unordered\" existing properties.
(This section is non-normative)
attachment : There is nothing that requires attachments to an object to be ordered; however, it is a popular expectation in several cases that the attachments should be ordered. For example, someone authoring an object may wish to attach three images in order, representing a triptych. If the array order of attachment were to be changed, this would change the author's intended representation.
tag : Tags are generally freeform and not meant to be parsed in any order, but some use-cases may wish to order tags as well. For example, Tumblr allows users to set the order their tags are presented in, and users sometimes use these tags to communicate a series of comments or thoughts without having them be present in the conversational context.
name : A thing may have multiple names, some of which are preferred more than others. Having an array for name is likely to confuse existing implementations at the time of writing this FEP, but it is something that could conceivably be useful for several use-cases.
oneOf/anyOf : When a Question represents a poll with predefined options for possible answers, the order of the options may be relevant to understanding the poll. Consider a self-referential poll that asks you to predict which option will receive the most votes: \"option 2\", \"option 3\", or \"option 1\".
"},{"location":"fep/a070/#terms-defined-by-this-fep","title":"Terms defined by this FEP","text":"In the associated context, we define the following terms:
"},{"location":"fep/a070/#orderedattachment","title":"orderedAttachment","text":"URI : https://www.w3.org/ns/activitystreams#attachment
Notes : https://www.w3.org/TR/activitystreams-vocabulary/#dfn-attachment
JSON-LD definition:
{\n \"@context\": {\n \"orderedAttachment\": {\n \"@id\": \"https://www.w3.org/ns/activitystreams#attachment\",\n \"@type\": \"@id\",\n \"@container\": \"@list\"\n }\n }\n}\n"},{"location":"fep/a070/#orderedtag","title":"orderedTag","text":"Term : orderedTag
URI : https://www.w3.org/ns/activitystreams#tag
Notes : https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tag
JSON-LD definition:
{\n \"@context\": {\n \"orderedTag\": {\n \"@id\": \"https://www.w3.org/ns/activitystreams#tag\",\n \"@type\": \"@id\",\n \"@container\": \"@list\"\n }\n }\n}\n"},{"location":"fep/a070/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/a4ed/","title":"FEP-a4ed: The Fediverse Enhancement Proposal Process","text":"Authors Status Date received Date final Tracking issue Discussions Repository pukkamustard pukkamustard@posteo.netFINAL 2020-10-16 2021-01-18 #201 - codeberg"},{"location":"fep/a4ed/#summary","title":"Summary","text":"A Fediverse Enhancement Proposal (FEP) is a document that provides information to the Fediverse community. The goal of a FEP is to improve interoperability and well-being of diverse services, applications and communities that form the Fediverse.
This document describes the scope, format and process of publishing Fediverse Enhancement Proposals.
"},{"location":"fep/a4ed/#scope-and-objectives","title":"Scope and Objectives","text":"A Fediverse Enhancement Proposal (FEP) should be a concise and focused documentation of a specific topic that is of interest to the Fediverse community.
A proposal should always have the intention of improving the interoperability and well-being of diverse services, applications and community of the Fediverse.
The Fediverse includes applications, services and communities using the ActivityPub protocol and other protocols that foster decentralized and diverse social media and culture.
Proposals may include descriptions of technical protocols and mechanisms, documentation of experimental work or current best practices.
Proposals are not limited to technical topics and may focus on social and cultural aspects.
Proposals may be entertaining and humorous (unlike this proposal).
"},{"location":"fep/a4ed/#language-document-structure-and-format","title":"Language, Document Structure and Format","text":"All Fediverse Enhancement Proposals must be written in English, be properly formatted as CommonMark and be reasonably grammatical.
Authors should use inclusive language and examples and refrain from using oppressive terminology.
"},{"location":"fep/a4ed/#proposal-title-and-identifier","title":"Proposal Title and Identifier","text":"Every Fediverse Enhancement Proposal must have a descriptive title.
An identifier is computed from the proposal title as the first 4 digits of the sha256 hash (in hex). The identifier can be computed from the title with standard Unix tools:
$ echo -n \"The Fediverse Enhancement Proposal Process\" | sha256sum | cut -c-4\na4ed\n By using the hash of the title as identifier we reduce the burden on facilitators to assign unique ids. This requires proposal titles to be unique.
"},{"location":"fep/a4ed/#proposal-type","title":"Proposal Type","text":"Fediverse Enhancement Proposals are classified into following types:
The type is chosen by the author. If the type is not specified, the proposal is assumed to be informational.
"},{"location":"fep/a4ed/#metadata","title":"Metadata","text":"Proposal metadata is placed at the top of the document as key-value pairs between opening and closing ~---~.
Following metadata key-value pairs may be placed at the top of a proposal:
type: Indicates the type of the proposal. Can be either informational or implementation.slug: the proposal identifier. The value must be enclosed within double quote marks, e.g. \"a4ed\".authors: A comma separated list of authors of the proposal. Each item of the list should contain an author's name followed by a contact address in angle brackets (e.g. Alyssa P. Hacker <alyssa.p.hacker@email.example>, Alyssa P. Hacker <@alyssa.p.hacker@fediverse.example>).status: Indicates the proposal status. Can be either DRAFT, WITHDRAWN or FINAL.dateReceived: Date of when the proposal was added to the repository (when status is set to DRAFT).dateWithdrawn: Date of when the proposal status was set to WITHDRAWN (only for proposals with status WITHDRAWN).dateFinalized: Date of when the proposal status was set to FINAL (only for proposals with status FINAL).trackingIssue: Link to the tracking issue for the proposal.discussionsTo: Link to the discussion.relatedFeps: A comma separated list of related FEPs (e.g. FEP-a4ed, FEP-141a, FEP-686f).replaces: A comma separated list of FEPs that are replaced by the proposal.replacedBy: Identifier of a FEP that replaces the proposal.Every FEP should include at least the following sections:
Following sections may be included in a proposal:
Fediverse Enhancement Proposals must be placed in the public domain by the authors with a CC0 1.0 Universal (CC0 1.0) Public Domain Dedication.
"},{"location":"fep/a4ed/#the-fediverse-enhancement-proposals-process","title":"The Fediverse Enhancement Proposals Process","text":"stateDiagram-v2\n accTitle: FEP process flowchart\n accDescr {\n The process begins from the \"DRAFT\" status, followed by either of the \"FINAL\" or \"WITHDRAWN\" status.\n The \"WITHDRAWN\" status can go back to the \"DRAFT\" status.\n }\n [*] --> DRAFT\n DRAFT --> FINAL\n DRAFT --> WITHDRAWN\n WITHDRAWN --> DRAFT\n FINAL --> [*] SUBMISSION.md file for a list of accepted submission methods.DRAFT and is added to the repository. The facilitator also creates a tracking issue for the proposal and adds trackingIssue attribute to the proposal metadata.slug, authors, status, dateReceived, trackingIssue and discussionsTo.FINAL or WITHDRAWN, facilitator adds dateFinalized or dateWithdrawn attributes to the proposal metadata.DRAFT:WITHDRAWN.FINAL.WITHDRAWN.FINAL can not be changed or updated in a way that would lead to adjustments to implementations. Minor corrections are allowed.Any substantial change to finalized proposal must be submitted as a separate FEP.
The new FEP MUST include a replaces metadata attribute pointing to FEPs it supersedes.
If the new FEP becomes FINAL, the original one MUST point to it in an added replacedBy metadata attribute.
A proposal with status WITHDRAWN remains in the repository and can be resubmitted.
FINAL a facilitator will archive all discussions linked in the tracking issue and add the resulting archive links to the tracking issue.A list of facilitators is maintained in the FACILITATORS.md file at the root of the FEP repository.
A list of methods in which a proposal may be submitted is maintained in the SUBMISSION.md file at the root of the FEP repository.
FEP-a4ed (The Fediverse Enhancement Proposal Process) is a living document and can be updated despite having the FINAL status.
Anyone can propose a change to FEP-a4ed (or other documents related to the FEP process) using any method listed in SUBMISSION.md file. The change must be approved by at least two facilitators (one of them could be the submitter). For any part of the FEP process, there must be a sufficient number of facilitators who agreed to do the maintenance work. Changes should not be accepted before at least 1 month passes after the last update, to give facilitators and the community time to review them and provide feedback.
Minor changes (i.e. a correction of a typo or of a broken link) can be accepted immediately after getting two approvals from facilitators.
"},{"location":"fep/a4ed/#history","title":"History","text":"The process and format described in this proposal is influenced by other community driven documentation efforts such as the BitTorrent Enhancement Proposal Process, Scheme Request for Implementation and the IETF RFC Series.
"},{"location":"fep/a4ed/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/a5c5/","title":"FEP-a5c5: Web Syndication Methods","text":"Authors Status Date received Tracking issue Discussions Repository AvidSeeker avidseeker7@protonmail.comDRAFT 2024-07-15 #368 Discussions codeberg"},{"location":"fep/a5c5/#summary","title":"Summary","text":"This document proposes a standard for web syndication methods across the Fediverse by appending .rss or .atom to object URLs. This will allow users to easily subscribe to feeds of timelines, posts, and other objects. Additionally, this proposal addresses whether syndication methods should be applicable to mirrored profiles across the Fediverse, recommending optional but preferred implementation.
To create a syndication feed, servers must append .rss or .atom to the URL of an object. For example:
https://example.org/@user.rss or https://example.org/@user.atomhttps://example.org/posts/12345.rss or https://example.org/posts/12345.atomFor mirrored profiles, such as https://example-mirror.org/@user@example.org, appending .rss or .atom should be supported, but it is optional. The recommendation is to implement this feature to maintain consistency and user convenience across different instances.
Example URLs:
https://example-mirror.org/@user@example.org.rss or https://example-mirror.org/@user@example.org.atomCC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/a974/","title":"FEP-a974: All Actor types should be followable","text":"Authors Status Date received Tracking issue Discussions Repository James Smith james@floppy.org.ukDRAFT 2025-02-05 #487 Discussions codeberg"},{"location":"fep/a974/#summary","title":"Summary","text":"In order to foster interoperability and good semantics, any valid unblocked Actor should be visible and followable on any platform when searched for. The type of the Actor should not matter for initial following, though can be used later as appropriate.
In order to federate, an ActivityPub service will expose a set of Actors; these are ActivityPub objects that (a) can perform activities and (b) can be followed (see the ActivityPub Specification).
Each Actor has a type. The Activity Vocabulary defines five \"core\" types: Person, Group, Service, Organization, and Application. These five types work well for many applications, but not all, and ActivityPub explicitly allows Actors to have any type.
In many services, it is desirable to use other object types for actors; either defined Object types or even custom types. For instance:
OrderedCollection, rather than a Group.OrderedCollection, or could even a custom Playlist type if the platform wanted to communicate specific semantics.However, some ActivityPub platforms may choose to only show actors of the five core types in search; if a platform wanted to use more semantically-meaningful types for its actors, those actors would be unavailable on such sites, which will inevitably lead to bug reports to the services using the non-core types.
While filtering at the Activity/Object type is sensible and inevitable for any ActivityPub platform, filtering at the Actor type level limits the ability of new services to use semantically-correct types as appropriate, and will over time make the type effectively useless, as new services will choose one of the five core types even if inappropriate, in order for their actors to be visible.
This issue has been discussed in the community before (see Mastodon issue #22322); this FEP attempts to move that discussion into a single simple statement of compatibility.
"},{"location":"fep/a974/#decision","title":"Decision","text":"Compliant ActivityPub services should not filter on Actor type in searches or actor-level activities (Follow, Accept, Undo, Block, etc). Any unblocked Actor should be followable on any service.
Services are of course free to filter activities that are delivered later on; this FEP puts no limit on that. The feed for an Actor that posts only Document object activities may appear completely empty to a user of a microblogging platform; it remains the right and privilege of any ActivityPub service to only process the activities it wants to.
However, by making all Actor types followable, new services can choose what activities to send where, safe in the knowledge that their actors will at least be visible and that the activities will be received.
A practical example: Manyfold allows Fediverse users to follow individual 3D Models (which could have a Document or even 3DModel actor type), and when they change, it posts Update activities where those models are the object. However, Manyfold knows that microblog applications don't understand those activities (and nor should they). Therefore, for compatibility, it sends what it terms a \"compabitility note\", a human-readable Create Note activity with the same information as the Update 3DModel, thus allowing microblog users to follow models and get updates wherever convenient. A decision on what to send where could use the compatibility detection proposed in FEP-9fde in future.
Potential negative impacts may happen if a service makes extra assumptions about an Actor based on its use of the core types (e.g. the background to Mastodon issue #22322 which talks about different semantics being used for Group actors), and discussion is invited on those impacts.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/ae0c/","title":"FEP-ae0c: Fediverse Relay Protocols: Mastodon and LitePub","text":"Authors Status Date received Date final Tracking issue Discussions Repository Steve Bate svc-fep@stevebate.netFINAL 2024-10-19 2025-03-14 #424 Discussions codeberg"},{"location":"fep/ae0c/#summary","title":"Summary","text":"Relays are important components within the decentralized Fediverse architecture. They act as intermediary servers that facilitate communication between different instances, enabling users on Fediverse platforms to share public content without requiring actor following relationships.
These relays benefit small instances by enabling them to effectively participate in the federated social network, both as consumers and producers of Fediverse content.
Several styles of relays existing in the Activity Fediverse. This FEP describe two popular styles of relays:
NOTE: This is an informational FEP documenting the current status quo. It uses RFC-2119 requirements keywords only as a convenience. Also, these are not standardized protocols. They will generally not be conformant with the ActivityPub standard although they use some concepts from it.
"},{"location":"fep/ae0c/#terminology","title":"Terminology","text":"For the purposes of this document, the following terminology will be used:
Term Description relay client actor An actor in a server that is a Relay Server subscriber. May also be referred to as a client actor. relay client server A server hosting one or more Relay Client Actors. May also be referred to as a client server. relay subscription A relationship established between a Relay Client Actor and a Relay Server using an ActivityPubFollow activity. relay server actor An actor in a server that provides relaying of messages between Relay Client Actors. May also be referred to as a relay actor. relay server A server hosting one or more Relay Server Actors. May also be referred to as a relay server or a relay. HTTP Signature HTTP-based signature (Cavage) used to verify message sender and contents. LD Signature JSON-LD signature used to verify messages regardless of transport."},{"location":"fep/ae0c/#mastodon-relay-protocol","title":"Mastodon Relay Protocol","text":"The Mastodon relay protocol relies on LD Signatures to verify relayed messages. This allows Mastodon to verify the relayed message although it is being sent by a different actor (the relay server actor).
"},{"location":"fep/ae0c/#relay-client-actor","title":"Relay Client Actor","text":"A Relay Client Actor establishes following relationship with a relay server actor and then processes relayed messages sent to the actor's ActivityPub inbox. The relay client server will add the relay inboxes to the delivery target for content with public visibility.
"},{"location":"fep/ae0c/#relay-subscription","title":"Relay Subscription","text":"Mastodon will POST an ActivityPub Follow request to the relay ActivityPub inbox URI. The object of the Follow request MUST be the fully expanded URI of the Public pseudo-collection (https://www.w3.org/ns/activitystreams#Public). The relay then responds to the Follow request with an Accept or Reject activity. The response time for the acknowledgement can be arbitrarily long since the subscription MAY require manual approval.
The request MUST be signed using the same HTTP Signature (Cavage) algorithms that Mastodon uses for ActivityPub federation. The relay will fetch the relay client actor document to obtain the actor's public key. For best interoperability, the actor ActivityPub document SHOULD be Mastodon-compatible. For example, preferredUsername SHOULD be provided in addition to all actor fields required by ActivityPub and the actor SHOULD provide a sharedInbox endpoint URL.
The relay client actor type SHOULD accurately reflect the actor type. However, note that some relay server implementations constrain the ActivityPub type of a client actor. For example, the relay server implementation might require the client actor to be an Application type and reject other types.
Example Follow Request
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://client.example/6ae15297\",\n \"type\": \"Follow\",\n \"actor\": \"https://client.example/actor\",\n \"object\": \"https://www.w3.org/ns/activitystreams#Public\"\n}\n Example Follow Accept Response
The Accept activity MAY respond with the accepted Follow activity URI as the object or it may embed a copy of the original Follow activity. A Reject activity will have a similar structure.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://relay.example/15c0b99f-23d4-4488-ba9d-d0c7bc2876a5\",\n \"type\": \"Accept\",\n \"actor\": \"https://relay.example/actor\",\n \"object\": {\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://client.example/6ae15297\",\n \"type\": \"Follow\",\n \"actor\": \"https://client.example/actor\",\n \"object\": \"https://www.w3.org/ns/activitystreams#Public\"\n }\n}\n"},{"location":"fep/ae0c/#relay-unsubscription","title":"Relay Unsubscription","text":"To unsubscribe from a relay send an Undo with the original Follow activity (embedded, or the URI) as the object. There is typically no response to the Undo.
Example Undo/Follow Request
{\n \"@context\": \"https: //www.w3.org/ns/activitystreams\",\n \"id\": \"https://client.example/3f5ebd6d\",\n \"type\": \"Undo\",\n \"actor\": \"https://client.example/actor\",\n \"published\": \"2024-10-14T14:42:17.650139+00:00\",\n \"object\": \"https://client.example/6ae15297\"\n}\n"},{"location":"fep/ae0c/#publishing-messages-to-a-relay","title":"Publishing Messages to a Relay","text":"To publish an activity to a Mastodon-style relay, the publisher MUST sign the message using the Mastodon-specific LD Signature algorithm. The benefit of using LD Signatures is that the receiving servers can verify the message content without refetching from the client server. This lowers the server load on the client server.
The disadvantage is that the LD Signatures are not easy to implement and Mastodon uses an outdated nonstandard form of the algorithm. The Mastodon documentation recommends not supporting LD Signatures for these reasons. Furthermore, the Mastodon documentation does not accurately describe the LD Signature algorithm it implements. For more details, see the additional information about Mastodon LD Signatures in this document.
The posted activity MUST be signed with a Mastodon-compatible HTTP Signature.
Mastodon will relay the following activity types: Create, Update, Delete, Move. A relay actor MAY only forward those types although Mastodon will accept other relayed activities, such as Announce, without an LD Signature. In the Announce case, it will fetch the announced object.
Relayed messages are posted to the relay client actor's inbox. Relayed messages MUST have an HTTP Signature signed by the Relay Actor.
Messages received from Relay Server Actors MAY have an LD Signature. If the HTTP Signature and the LD Signature are both present, the activity actor becomes the effective sender after LD Signature verification.
If no LD Signature is present and the received message is an Announce activity, then a relay client MUST ensure the content is legitimate (not spoofed). This may be done by fetching the announced activity from the originating server or using remote content from a local cache. However, if the announced activity is already locally cached, then there would typically be no processing to be done with it since it is already known to the client server.
A client server receiving a relayed message MAY also deliver the messages to local recipients based on the ActivityPub audience targeting properties.
"},{"location":"fep/ae0c/#relay-server-actor","title":"Relay Server Actor","text":"The following behaviors describe the typical implementation of a Mastodon-style relay server actor.
"},{"location":"fep/ae0c/#follow","title":"Follow","text":"Ensure that https://www.w3.org/ns/activitystreams#Public is in the object property. Use the actor as the relay client actor URI and save the information about the subscriber. The relay server MAY decide to deny access based on factors like the signer's domain.
Verify that the actor is a known relay client and, if so, remove the client actor from the set of relay actor followers.
When a message is received from a client actor, the relay MUST verify the HTTP Signature of the activity and identify the originating actor. If the message is valid, it is then posted (with the relay actor's HTTP Signature) to the inboxes of the relay's followers. No delivery is performed based on the ActivityPub audience targeting properties. The relay MUST not send the relayed message to the originating relay client actor.
Typically a message is forwarded unchanged. However, a relay MAY do other processing on the message, such as wrapping a message without an LD Signature using an ActivityPub Announce activity before forwarding it (See pub-relay). Extended behaviors like this are not described in this FEP.
A relay actor SHOULD only relay messages from followers. A relay actor SHOULD only relay activities that it hasn't already relayed. Addressing properties, like to, MUST be in a list even for a single URI.
Note that the Mastodon documentation for their LD Signatures is incomplete and inaccurate. This section provides more details, but it may be necessary to review the Mastodon source code for additional clarification.
An activity signed with a Mastodon LD Signature will have a signature document in the activity (using the signature property).
Example Signature Document
{\n \"@context\": [\n \"https: //www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/v1\"\n ],\n \"id\": \"https://client.example/3f5ebd6d\",\n # ...\n \"signature\": {\n \"type\": \"RsaSignature2017\",\n \"creator\": \"https://client.example/actor#main-key\",\n \"created\": \"2024-12-08T03:48:33.901Z\",\n \"signatureValue\": \"s69F3mfddd99dGjmvjdjjs81e12jn121Gkm1\"\n }\n}\n The https://w3id.org/security/v1 JSON-LD context defines the signature and related properties, but is not used by Mastodon for LD Signature processing.
When performing signature operations the signature document and the activity (without the signature document) are initially processed (hashed) separately. The SHA256 hash digests are concatenated that string is then signed.
"},{"location":"fep/ae0c/#signing-a-json-ld-activity","title":"Signing a JSON-LD Activity","text":"creator and created properties. Set the @context to https://w3id.org/identity/v1. (Note that this context doesn't appear to be accessible on the web any more. You may need a local copy with a custom JSON-LD context loader.) signatureValue of the signature document to the result. type to \"RsaSignature2017\". signature property of the activity to the signature document.signatureValue from the signature document.type and id and signatureValue properties from the signature document and generate a SHA256 hex digest for the modified signature document using the procedure described for signing documents.signature from the activity and generate a SHA256 hex digest for it.The LitePub protocol is based on ActivityPub and is used in Pleroma-compatible servers. The reference implementation is the Pleroma Relay.
"},{"location":"fep/ae0c/#relay-client","title":"Relay Client","text":"A LitePub relay client actor must have a type of Application and an actor ID ending with /relay. For best interoperability, it should be compatible with Mastodon actor documents and have WebFinger support. Other implementations may use different actor ID structures (e.g., AodeRelay apparently uses /actor and works with Pleroma). General relay interoperability of these LitePub variants is not known.
The client relay actor will send a Follow to the relay server. The Follow object is the relay server actor URI.
The relay server MUST respond to the Follow request with an Accept or Reject. If accepted, the relay server sends a reciprocal Follow request for the LitePub client actor. The client server SHOULD respond with Accept or Reject activity. A relay server MAY decide to ignore the subscription if no acknowledgement is received within a reasonable time interval.
Example Relay Follow Request
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://pleroma.example/schemas/litepub-0.1.jsonld\",\n {\n \"@language\": \"und\"\n }\n ],\n \"actor\": \"https://pleroma.example/relay\",\n \"bcc\": [],\n \"bto\": [],\n \"cc\": [],\n \"id\": \"https://pleroma.example/activities/3fe13910-73f4-4cdc-9c84-ec7013a3e764\",\n \"object\": \"https://relay.example/actor\",\n \"state\": \"pending\",\n \"to\": [\n \"https://relay.example/actor\"\n ],\n \"type\": \"Follow\"\n}\n Notes: 1. The JSON-LD context is not valid for JSON-LD processing. The litepub-0.1.jsonld document contains an invalid WebFinger-related context URL. 2. The state property is not defined in the JSON-LD context.
To unsubscribe from a relay send an Undo with the original Follow activity as the object. There is typically no response to the Undo.
Example Undo/Follow Request
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://pleroma.example/schemas/litepub-0.1.jsonld\",\n {\n \"@language\": \"und\"\n }\n ],\n \"id\": \"https://pleroma.example/activities/cf9c85e9-f83f-4a02-b598-880f15423f68\",\n \"object\": {\n \"actor\": \"https://pleroma.example/relay\",\n \"bcc\": [],\n \"bto\": [],\n \"cc\": [],\n \"context\": \"https://pleroma.example/contexts/d493d02b-7cc9-49dc-995c-d949af0b5417\",\n \"id\": \"https://pleroma.example/activities/3fe13910-73f4-4cdc-9c84-ec7013a3e764\",\n \"object\": \"https://relay.example/actor\",\n \"published\": \"2024-10-18T14:04:11.029802Z\",\n \"state\": \"cancelled\",\n \"to\": [\n \"https://relay.example/actor\"\n ],\n \"type\": \"Follow\"\n },\n \"published\": \"2024-10-18T14:04:11.029791Z\",\n \"to\": [ \"https://relay.example/actor\" ],\n \"cc\": [],\n \"type\": \"Undo\",\n \"actor\": \"https://pleroma.example/relay\",\n \"context\": \"https://pleroma.example/contexts/d493d02b-7cc9-49dc-995c-d949af0b5417\"\n}\n"},{"location":"fep/ae0c/#publishing-messages-to-a-relay_1","title":"Publishing Messages to a Relay","text":"A LitePub relay client actor will send an Announce for a relayed object (like a Note). For best interoperability Announce should refer to the announced object using an URI (instead of embedding the object).
The Announce activity MUST be address to the relay server actor's followers collection. (TODO it's not known if the admin addressing is also required). The published property should be included since some relay server will reject activities without it.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://pleroma.example/schemas/litepub-0.1.jsonld\",\n {\n \"@language\": \"und\"\n }\n ],\n \"actor\": \"https://pleroma.example/relay\",\n \"to\": [\n \"https://pleroma.example/relay/followers\",\n \"https://pleroma.example/users/admin\"\n ],\n \"bto\": [],\n \"cc\": [],\n \"context\": \"https://pleroma.example/contexts/a59117d9-7f7c-48ec-83b4-5e183e7179b5\",\n \"id\": \"https://pleroma.example/activities/e24e46a2-8926-4a20-9f5f-638e06102159\",\n \"object\": \"https://pleroma.example/objects/c13bba3c-e7c1-45ac-939f-aa292d23ee8c\",\n \"published\": \"2024-10-18T14:06:37.736295Z\",\n \"type\": \"Announce\"\n}\n"},{"location":"fep/ae0c/#receiving-messages-from-a-relay","title":"Receiving Messages from a Relay","text":"Messages received from a relay are typically wrapped in an Announce activity. AFter the object of the announce is fetched and validated, it is displayed on the federated timeline. It appears that Pleroma will accept a relayed Create activity (for Mastodon compatibility), but will refetch the Create object since the LD Signature isn't processed. (TODO verify this behavior.)
The relay server hosting relay actors will have other functionality other than activity relaying.
"},{"location":"fep/ae0c/#webfinger","title":"WebFinger","text":"A relay server MUST implement WebFinger support for relay actors. This is necessary because of Mastodon's actor fetching implementation. It's possible that it's not required for LitePub-only relay servers.
"},{"location":"fep/ae0c/#nodeinfo","title":"NodeInfo","text":"A relay server MAY implement NodeInfo to advertise server activity and metadata.
"},{"location":"fep/ae0c/#optional-relay-server-behaviors","title":"Optional Relay Server Behaviors","text":"A relay server MAY support multiple relay protocols. However, there is no standard way to advertise those capabilities.
A relay server often hosts a single actor, but any number of relay actors may be hosted. For example, a relay server may have a relay actor for specific topics, hashtags, or moderation categories. A relay client can subscribe to any number of relay actors in a given server.
Some servers implement dynamic relay actor creation. A relay actor's inbox URI might be based on a hashtag or a topic name. When a client actor subscribes to this kind of inbox URI, the relay actor is created automatically. Obviously, there are risks to this approach when used by misbehaved clients.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/ae97/","title":"FEP-ae97: Client-side activity signing","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2023-08-14 #148 Discussions codeberg"},{"location":"fep/ae97/#summary","title":"Summary","text":"Existing Fediverse servers manage signing keys on behalf of their users. This proposal describes a new kind of ActivityPub client that lets users sign activities with their own keys, and a server that can distribute client-signed activities to other servers.
"},{"location":"fep/ae97/#history","title":"History","text":"Initial version of this proposal relied on linking of cryptographic identities to actor objects via FEP-c390 identity proofs. That mechanism was superseded by FEP-ef61 which achieves full data portability.
"},{"location":"fep/ae97/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/ae97/#discovery","title":"Discovery","text":"A server that supports clients capable of managing signing keys MUST have a discovery endpoint at the path /.well-known/apgateway.
When the server receives an HTTP GET request to this endpoint, it MUST respond with a JSON object containing information about itself. That object MAY be empty.
Example:
{\n \"uploadMedia\": \"https://gateway.example/.well-known/apgateway-media\"\n}\n"},{"location":"fep/ae97/#registering-an-actor","title":"Registering an actor","text":"Client creates portable actor objects according to FEP-ef61 and stores them. When a portable actor is created, the client MUST create a key for signing requests (the main actor key) and add a Multikey representation of it to the assertionMethod array as described in FEP-521a. The identifier of that key MUST NOT be a compatible identifier.
Before registering a portable actor on the server, the client MUST add the server URL to the gateways array of the actor object.
To register the actor, the client sends an HTTP POST request to the gateway endpoint at /.well-known/apgateway path. The body of the request MUST be an actor object.
The server SHOULD limit registrations (for example, by requiring an invite code). If the server accepts the registration request, it generates an RSA key and returns it in a response. The response MUST have the 201 Created status code. The body of the response is a JSON object with an assertionMethod property. The value of this property is an array containing the RSA public key generated by the server in the Multikey format.
Example:
{\n \"assertionMethod\": [\n {\n \"type\": \"Multikey\",\n \"publicKeyMultibase\": \"z4MXj1wBzi9jUstyPMS4jQqB6KdJaiatPkAtVtGc6bQEQEEsKTic4G7Rou3iBf9vPmT5dbkm9qsZsuVNjq8HCuW1w24nhBFGkRE4cd2Uf2tfrB3N7h4mnyPp1BF3ZttHTYv3DLUPi1zMdkULiow3M1GfXkoC6DoxDUm1jmN6GBj22SjVsr6dxezRVQc7aj9TxE7JLbMH1wh5X3kA58H3DFW8rnYMakFGbca5CB2Jf6CnGQZmL7o5uJAdTwXfy2iiiyPxXEGerMhHwhjTA1mKYobyk2CpeEcmvynADfNZ5MBvcCS7m3XkFCMNUYBS9NQ3fze6vMSUPsNa6GVYmKx2x6JrdEjCk3qRMMmyjnjCMfR4pXbRMZa3i\"\n }\n ]\n}\n If the server can't register the actor, it MUST return a 400 Bad Request status code.
If the registration is successful, the client MUST attach the RSA key to the actor object via publicKey property, and also add it to the assertionMethod array as described in FEP-521a. If the server's response contains other keys, they SHOULD be added to the assertionMethod array as well.
If the client uses compatible identifiers, then key identifiers MUST be generated with the server's origin.
After updating the actor object, the client MUST publish an Update activity for it.
The client submits signed FEP-ef61 activities to actor's outbox. Contrary to what ActivityPub specification prescribes in section 6. Client to Server Interactions, the server MUST NOT overwrite the ID of an activity. Instead of assigning a new ID, the server MUST verify that provided ID has not been used before. If the server accepts activity, its response MUST have 202 Accepted status code.
If activity contains a wrapped object (as in Create and Update activities), it MUST be a portable object created according to FEP-ef61. The server MUST validate object IDs in the same way it validates activity IDs.
The server MUST deliver activities to their indended audiences without altering them. When signing HTTP requests, the server uses the RSA key generated during the registration.
If the owner of the outbox is not registered, the server MUST return a 404 Not Found status code.
If the actor of the submitted activity is different from the outbox owner, the server MUST return a 403 Forbidden status code.
Client receives activities by polling the actor's inbox.
Requests to inbox endpoint MUST have an HTTP signature created using the main actor key (the one generated by the client, not a server-generated key).
If the signature is not valid, the server MUST return a 401 Unauthorized status code.
If the owner of the inbox is not registered, the server MUST return a 404 Not Found status code.
If the actor that generated the signature is different from the inbox owner, the server MUST return a 403 Forbidden status code.
To upload a media file, the client sends an HTTP POST request to the gateway endpoint at the /.well-known/apgateway-media path. The body of the request MUST be the media in the form of binary data. The request MUST contain a Content-Type header and MUST be signed with the main key of the actor.
If the media is processed successfully, the server MUST return a response with a 201 Created status code and a JSON object as the body. This JSON object MUST have a url property whose value is a hashlink to the processed media file.
Example:
{\n \"type\": \"Document\",\n \"url\": \"hl:zQmWvQxTqbG2Z9HPJgG57jjwR154cKhbtJenbyYTWkjgF3e\"\n}\n The server MUST serve the processed media file at a /.well-known/apgateway-media/{hashlink} path.
If the signature is not valid, the server MUST return a 401 Unauthorized status code. If the signature is valid, but the actor is not registered, the server MUST return a 403 Forbidden status code.
If the media type is not supported, the server MUST return a 400 Bad Request status code.
If the media is too large, the server MUST return a 413 Payload Too Large status code.
To delete a previously uploaded media file, the client sends an HTTP DELETE request to an endpoint at path /.well-known/apgateway-media/{hashlink}. The request MUST be signed with the main key of the actor.
If the signature is valid and the actor owns the media file, the server MUST delete the file.
If the signature is not valid, the server MUST return a 401 Unauthorized status code. If the signature is valid, but the actor is not registered, the server MUST return a 403 Forbidden status code.
If the media file doesn't exist or not owned by the actor, the server MUST return a 404 Not Found status code.
If the server accepts portable objects with compatible identifiers, it MUST ensure that all objects served by the gateway are valid and that registered actors are sufficiently isolated from each other. Specifically, the server MUST verify that actors and activities generated by the client are permitted in the origin-based security model:
Precautions need also be taken when objects with compatible IDs are delivered to inboxes or fetched from remote servers.
"},{"location":"fep/ae97/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/b06c/","title":"FEP-b06c: ActivityPoll","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.orgDRAFT 2025-06-25 #638 Discussions codeberg"},{"location":"fep/b06c/#summary","title":"Summary","text":"ActivityPoll is a proper subset of ActivityPub that excludes activity delivery, making it easier to implement for static Web sites or content management systems. It meets an equivalent need to RSS or Atom feeds.
"},{"location":"fep/b06c/#introduction","title":"Introduction","text":"In ActivityPub (AP), an actor is an ActivityPub object that produces activities. The actor's outbox is the collection of activities that the actor has produced.
ActivityPub has optional features for real-time delivery of activities. ActivityPub actors MAY deliver new activities to the actors addressed in the activity's to, cc, bto, bcc and audience properties using the ActivityPub federation protocol. The actor MAY also receive activities from other actors in its inbox, such as comments, likes, and other feedback.
Supporting the federation protocol adds significant complexity in the implementation of ActivityPub. Handling incoming and outgoing activities is a significant barrier to entry for AP implementation.
One alternative is to shift the burden of initiating delivery from the publisher to the consumer. Instead of pushing activities from the publishing server as they are generated, the activities can instead be pulled by the consumer at regular intervals.
There are three main downsides to a polling structure.
inbox.The primary upside is that polling opens up the social web to many more implementers. Any publisher that can publish an RSS feed can publish an ActivityPoll actor. Polling also opens up the network to simpler, power-constrained devices, as well as static web sites.
Having more content producers is better for all users, and thus better for more mature and full-featured social web platforms. Shifting this burden of delivery, if it results in more information on the network, can be good for the entire ecosystem.
This subset of ActivityPub, called ActivityPoll, describes the reduced responsibilities of the publisher, the increased responsibilities of the consumer, and methods for indicating use of polling.
"},{"location":"fep/b06c/#publishers","title":"Publishers","text":"ActivityPoll publishers host one or more ActivityPoll actors. ActivityPoll actors are valid ActivityPub actors and ActivityPub objects. They MUST have at least the following properties:
id: A unique identifier for the actor, which MUST be a dereferenceable IRI.type: The type of the actor.outbox: An OrderedCollection of activities, with its own dereferenceable IRI.inbox: An OrderedCollection of activities, with its own dereferenceable IRI.Other properties of an Activity Streams 2.0 object MAY be included, including extension properties.
Other properties of an ActivityPub actor object MAY be included.
"},{"location":"fep/b06c/#inbox","title":"Inbox","text":"If an actor is poll-only, and does not support the ActivityPub federation protocol, the IRI of the inbox property of the actor MUST respond with a 405 Method Not Allowed response to HTTP POST requests.
Publishers MAY also indicate that delivery is not supported using the pollOnly property defined in this specification, setting it to true. This does not override the requirement to have an inbox property that returns a 405 HTTP status on POST.
The actor's outbox property is an OrderedCollection in reverse chronological order. It MAY be paginated.
The IRI for the outbox collection SHOULD support HTTP Caching, including the If-Modified-Since and If-None-Match request headers. The IRI for each page of a paginated outbox collection SHOULD support HTTP Caching.
The outbox collection object SHOULD include a totalItems property, so that changes in any page of the collection are reflected in the collection itself.
Activities in the outbox, and all the object properties of the activities, MUST be valid ActivityPub objects, with dereferenceable IRIs.
To make incremental polling manageable, ordering of activities in the outbox collection SHOULD be stable. New activities SHOULD be prepended to the collection. Deleted activities SHOULD be replaced by a Tombstone object.
ActivityPoll servers MAY require authentication, such as OAuth 2.0 or HTTP Signatures. Collections such as outbox MAY be filtered according to the access level of the authenticated client.
Without an inbox that can receive incoming activities, an ActivityPoll publisher might not maintain many of the state changes that are typical side effects of such activities. A non-exhaustive list of collections affected by inbound activities:
followers collection for an actorreplies collection for an objectshares collection for an objectlikes collection for an objectPublishers SHOULD omit these properties from objects and actors if they will not be maintained.
"},{"location":"fep/b06c/#webfinger","title":"Webfinger","text":"ActivityPoll servers MAY implement Webfinger to allow user@hostname addressing for actors. Because Webfinger requires different content based on a query parameter, it is difficult to implement for static web sites, unless they only support a single actor.
ActivityPoll consumers read the outbox collection of an actor to retrieve activities and distribute them.
ActivityPoll consumers SHOULD support HTTP Caching when fetching the OrderedCollection, its pages, and any activities and related objects in the collection.
Because the outbox property is ordered in reverse chronological order, consumers SHOULD use the results of their last poll to limit requests to the publishing server. Keeping a \"last read\" activity ID between polls can be used to limit the requests to only activities and pages published since that ID.
ActivityPoll consumers SHOULD include remote ActivityPoll actors in their actors' following collections.
Polling frequency in syndication systems is important. If polling is too infrequent, new activities aren't processed in a timely fashion. If polling is too frequent, it abuses publisher and consumer resources without any new activities being delivered.
The updateInterval property of an actor can indicate how often the actor generates activities. Consumers SHOULD use this information to schedule polling requests. Consumers MAY use heuristics to schedule polling requests, such as median time between activities previously seen. Consumers MAY set a minimum frequency for polling actors, such as daily or monthly.
If the addressing properties of an activity by an ActivityPoll actor include actors on the consumer server, the consumer SHOULD deliver the activities to those actors' inbox collections. ActivityPoll consumers MAY deliver activities by ActivityPoll publisher actors to other servers, for example for inbox forwarding.
Some activities, or their objects, MAY be added to special collections on the consumer server. These include:
inReplyTo property with the value of an ActivityPub object on the consuming server MAY be added to the replies collection for that object.Like activities with an object value on the consumer server MAY be added to the likes collection of that object.Announce activities with an object value on the consumer server MAY be added to the shares collection of that object.Follow activities with an object value on the consumer server MAY result in the actor being added to the object value's followers collection.Other side effects MAY be implemented by the consuming server.
"},{"location":"fep/b06c/#context","title":"Context","text":"This specification includes a context document that defines the optional properties of an ActivityPoll actor.
{\n \"@context\": {\n \"poll\": \"https://w3id.org/fep/b06c#\",\n \"pollOnly\": \"poll:pollOnly\",\n \"updateInterval\": \"poll:updateInterval\"\n }\n}\n The context can be included using the context URL https://w3id.org/fep/b06c, as illustrated in the examples below.
pollOnly","text":"This is a boolean property that can be used to determine if an actor does not deliver activities remotely. Its default value is false.
updateInterval","text":"This property represents a typical interval between activities in the outbox of the actor. It is expressed as an xsd:duration value. Some example values:
P15M - new activities are added every 15 minutesP4H - new activities are added once every 4 hoursP1D - updated dailyP3M - updated quarterlyP1Y - updated annuallyThis interval conveys a rough estimate of update frequency, not a commitment. Different types of actors often have different posting profiles. Automated actors often have regular intervals for activities. Real-world humans, on the other hand, tend to cluster into \"sessions\", when a human being makes a number of activities in only a few minutes or hours.
"},{"location":"fep/b06c/#examples","title":"Examples","text":""},{"location":"fep/b06c/#minimal-activitypoll-actor","title":"Minimal ActivityPoll Actor","text":"This minimal actor provides just enough information to be a useful ActivityPoll object.
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/b06c\"],\n \"id\": \"https://device.example/actor.jsonld\",\n \"type\": \"Application\",\n \"name\": \"Low-power device #69883\",\n \"to\": \"as:Public\",\n \"pollOnly\": true,\n \"inbox\": \"https://device.example/actor/inbox.jsonld\",\n \"outbox\": \"https://device.example/actor/outbox.jsonld\"\n}\n The inbox for this actor is an empty collection.
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/5711\"],\n \"id\": \"https://device.example/inbox.jsonld\",\n \"type\": \"OrderedCollection\",\n \"summary\": \"Inbox of low-power device #69883\",\n \"attributedTo\": \"https://device.example/actor.jsonld\",\n \"to\": \"as:Public\",\n \"inboxOf\": \"https://device.example/actor.jsonld\",\n \"totalItems\": 0\n}\n The outbox for this actor is not paginated.
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/5711\"],\n \"id\": \"https://device.example/outbox.jsonld\",\n \"type\": \"OrderedCollection\",\n \"summary\": \"Outbox of low-power device #69883\",\n \"attributedTo\": \"https://device.example/actor.jsonld\",\n \"to\": \"as:Public\",\n \"outboxOf\": \"https://device.example/actor.jsonld\",\n \"totalItems\": 5,\n \"items\": [\n \"https://device.example/create-note-3.jsonld\",\n \"https://device.example/like-note-1.jsonld\",\n \"https://device.example/delete-note-2.jsonld\",\n \"https://device.example/create-note-2.jsonld\",\n \"https://device.example/create-note-1.jsonld\"\n ]\n}\n"},{"location":"fep/b06c/#publication","title":"Publication","text":"This example models a simple ActivityPoll actor for a Web magazine. By embedding node objects into the document, the publisher can dramatically reduce the number of HTTP requests required to check for updates.
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/b06c\"],\n \"id\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"type\": \"Organization\",\n \"name\": \"ActivityPoll Magazine\",\n \"summary\": \"The premier magazine for the poll-oriented social web\",\n \"pollOnly\": true,\n \"updateInterval\": \"P1D\",\n \"inbox\": \"https://publisher.example/magazine/activitypoll/inbox.jsonld\",\n \"outbox\": {\n \"id\": \"https://publisher.example/magazine/activitypoll/outbox.jsonld\",\n \"totalItems\": 4,\n \"first\": {\n \"id\": \"https://publisher.example/magazine/activitypoll/outbox/1.jsonld\",\n \"orderedItems\": [\n {\n \"id\": \"https://publisher.example/magazine/activitypoll/create/article/3.jsonld\",\n \"type\": \"Create\",\n \"summary\": \"ActivityPoll Magazine created an article\",\n \"actor\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\",\n \"object\": {\n \"id\": \"https://publisher.example/magazine/activitypoll/article/3.jsonld\",\n \"type\": \"Article\",\n \"name\": \"Polling strategies for ActivityPoll consumers\",\n \"summary\": \"<p>An overview of strategies for polling intervals</p>\",\n \"url\": \"https://publisher.example/magazine/activitypoll/article/3.html\",\n \"attributedTo\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\"\n },\n \"published\": \"20250621T00:00:00Z\"\n },\n {\n \"id\": \"https://publisher.example/magazine/activitypoll/delete/article/2.jsonld\",\n \"type\": \"Delete\",\n \"summary\": \"ActivityPoll Magazine deleted an article\",\n \"actor\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\",\n \"object\": {\n \"id\": \"https://publisher.example/magazine/activitypoll/article/2.jsonld\",\n \"type\": \"Tombstone\",\n \"formerType\": \"Create\",\n \"published\": \"20250615T00:00:00Z\",\n \"deleted\": \"20250618T00:00:00Z\",\n \"attributedTo\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\"\n },\n \"published\": \"20250618T00:00:00Z\"\n },\n {\n \"id\": \"https://publisher.example/magazine/activitypoll/create/article/2.jsonld\",\n \"type\": \"Create\",\n \"summary\": \"ActivityPoll Magazine created an article\",\n \"actor\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\",\n \"object\": {\n \"id\": \"https://publisher.example/magazine/activitypoll/article/2.jsonld\",\n \"type\": \"Tombstone\",\n \"formerType\": \"Create\",\n \"published\": \"20250615T00:00:00Z\",\n \"deleted\": \"20250618T00:00:00Z\",\n \"attributedTo\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\"\n },\n \"published\": \"20250615T00:00:00Z\"\n },\n {\n \"id\": \"https://publisher.example/magazine/activitypoll/create/article/1.jsonld\",\n \"type\": \"Create\",\n \"summary\": \"ActivityPoll Magazine created an article\",\n \"actor\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\",\n \"object\": {\n \"id\": \"https://publisher.example/magazine/activitypoll/article/1.jsonld\",\n \"type\": \"Article\",\n \"name\": \"Announcing ActivityPoll Magazine\",\n \"summary\": \"<p>This is a new magazine for posts about the poll-oriented social web.</p>\",\n \"url\": \"https://publisher.example/magazine/activitypoll/article/1.html\",\n \"attributedTo\": \"https://publisher.example/magazine/activitypoll.jsonld\",\n \"to\": \"as:Public\"\n },\n \"published\": \"20250609T00:00:00Z\"\n }\n ]\n }\n }\n}\n"},{"location":"fep/b06c/#social-network-interactions","title":"Social Network Interactions","text":"This example models a simple social network user, with reaction activities to content published elsewhere.
{\n \"@context\": [\"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/b06c\"],\n \"id\": \"https://social.example/user/activitypoll.jsonld\",\n \"type\": \"Person\",\n \"name\": \"Social Networkuser\",\n \"summary\": \"A typical social network user\",\n \"pollOnly\": true,\n \"updateInterval\": \"P8H\",\n \"inbox\": \"https://social.example/user/activitypoll/inbox.jsonld\",\n \"outbox\": {\n \"id\": \"https://social.example/user/activitypoll/outbox.jsonld\",\n \"totalItems\": 3,\n \"first\": {\n \"id\": \"https://social.example/user/activitypoll/outbox/1.jsonld\",\n \"orderedItems\": [\n {\n \"id\": \"https://social.example/user/activitypoll/activity/3.jsonld\",\n \"type\": \"Like\",\n \"summary\": \"Social Networkuser liked this object\",\n \"object\": \"https://other.example/object/D6D11370-8BD1-4E52-849B-AF62121AB58E\"\n },\n {\n \"id\": \"https://social.example/user/activitypoll/activity/2.jsonld\",\n \"type\": \"Announce\",\n \"summary\": \"Social Networkuser shared this object\",\n \"object\": \"https://third.example/object/50D4C4E9-DEB4-4EC5-8353-F079A63533F0\"\n },\n {\n \"id\": \"https://social.example/user/activitypoll/activity/3.jsonld\",\n \"type\": \"Create\",\n \"summary\": \"Social Networkuser created a note\",\n \"object\": {\n \"id\": \"https://social.example/user/activitypoll/note/1.jsonld\",\n \"type\": \"Note\",\n \"inReplyTo\": \"https://other.example/object/0BDF12F8-1148-4BDF-B911-3B9CBEA95678\",\n \"content\": \"I agree!\"\n }\n }\n ]\n }\n }\n}\n"},{"location":"fep/b06c/#privacy-considerations","title":"Privacy considerations","text":"This specification allows publishers to implement authentication for ActivityPub objects, actors, and collections. However, authentication is difficult to implement for static web sites. Features that depend on authentication, such as non-public objects or blocking users or domains, will be hard to implement or enforce with ActivityPoll.
"},{"location":"fep/b06c/#implementation-notes","title":"Implementation notes","text":"The subset of ActivityPub outlined in this specification does not preclude the use of the ActivityPub API for publishing activities, even if they are not delivered to remote addressees.
"},{"location":"fep/b06c/#history","title":"History","text":"Read-only, poll-based syndication feeds are a common pattern on the Web. RSS 1.0, RSS 2.0, Atom, and other syndication formats have been used for decades to provide a simple way to transfer content from one Web site to another. Atom Activity Streams 1.0 enhanced Atom with richer data about social networking activities, such as comments, likes, and shares.
"},{"location":"fep/b06c/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/b2b8/","title":"FEP-b2b8: Long-form Text","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.orgDRAFT 2024-11-07 #441 Discussions codeberg"},{"location":"fep/b2b8/#summary","title":"Summary","text":"Multi-paragraph text is an important content type on the Social Web. This FEP defines best practices for representing and using properties of a long-form text object in Activity Streams 2.0.
"},{"location":"fep/b2b8/#motivation","title":"Motivation","text":"Blog posts, magazine articles, and forum posts are often made up of multiple paragraphs of text, sometimes with embedded images, video, audio or other media. This important content type is documented in the Activity Vocabulary, but this FEP provides additional guidance for publishers and consumers and collects the relevant properties in one place.
Well-defined behaviour for supporting long-form text provides multiple benefits. Not only does it allow publishers to integrate content in a dependable way across different platforms, but it also gives users of Activity Streams 2.0 consumer applications more control over their reading experience -- including filtering or sorting long-form text objects in their stream.
This FEP does not provide guidance for book-length or longer text.
This document provides information for multiple protocols that use Activity Streams 2.0 as a representation format. Where ActivityPub use is different than AS2, it is noted.
Because long-form text is often syndicated using RSS 2.0, the properties in this FEP are compared to the properties in that format where appropriate.
"},{"location":"fep/b2b8/#type","title":"Type","text":"The Article type is used to represent multi-paragraph text. The Activity Streams 2.0 primer provides guidance on when to use the Article type and when to use the Note type.
Some consumers do not display Article objects with their full content. Some publishers work around this by using a Note-type object with much more content than expected for a note.
Publishers should avoid this workaround, and instead give consumers the full information they need to display the content correctly in their own interfaces. The preview property can be used to provide a simpler version of the content for consumers that don't support Article directly.
Forcing long-form text into a Note object can cause problems for consumers that expect Note objects to be short and well-formatted for stream display. Maintaining a clear distinction between Note and Article objects is important for interoperability.
Consumers that only display short text should show the name, summary and a link to the url property so that users can view the full content in a web browser. As a fallback, they can use the preview property if it is present.
id","text":"A unique identifier for the text. For ActivityPub, this should be an HTTPS URL that resolves to the object. It should be a single string, unique for all objects.
This property provides the same functionality as the guid property in RSS 2.0.
name","text":"The title of the text should be in the name property. The property should be short enough to be displayed in a line or two on a browser interface; 75-150 characters is a good rule of thumb. Longer descriptions should be in the summary property.
The name property should be plain text, not HTML or other markup. In particular, no HTML entities like & or < should be used.
This property provides the same functionality as the title property in RSS 2.0.
url","text":"The location of the full text should be in the url property. This can be a single string, in which case it is the URL of the HTML representation of the text. It can also be a Link object, which can include additional metadata about the link. If it is a Link object, the mediaType should be 'text/html' and href property should be the URL of the HTML representation of the text.
The url property can also be an array of strings or Link objects or both. Multiple Link objects can be used to represent different media types or provide different URL protocols. At least one of the Link objects should have a mediaType of 'text/html' and a href property with the protocol 'https'.
This property provides the same functionality as the link property in RSS 2.0.
summary","text":"This property provides a brief description, teaser, abstract or \"lede\" for the text. It should be a maximum of about 500 characters; a few sentences; or a short paragraph.
This property can include HTML markup. It should not include embedded media like images, video or audio. It should not include navigation or interaction elements like \"favourite\", \"like\", \"bookmark\" or other buttons. It should not include links to the publisher's home page or category pages. It should not include a \"Read more...\" link to the full text.
This property provides the same functionality as the description property in RSS 2.0.
attributedTo","text":"This property provides the authors of the text, either as a string, an object, or an array.
As a string, it is a single id for the author. For ActivityPub, the id should be a URL that resolves to an ActivityPub actor.
As an object, it can be an AS2 object with a type like Person, Application or Organization. It should have an id and a name property and can also include an icon property for the author's avatar. A summary property can be used to provide a brief description of the author, including HTML. An url property can be used to provide a link to the author's profile page.
If the author does not have an AS2 representation, the attributedTo property can be an object with a type of Link and an href property with the URL of the author's profile page. The name property can be used to provide the author's name.
As an array, the attributedTo property can include multiple authors, either as strings or objects.
The attributedTo property provides the same functionality as the author property of an item in RSS 2.0, with additional features.
published","text":"The publication date of the text should be in the published property. This should be a dateTime string in the format YYYY-MM-DDTHH:MM:SSZ.
This property provides the same functionality as the pubDate property in RSS 2.0.
updated","text":"If the object has been updated, the date of the last update should be in the updated property. This should be a dateTime string in the format YYYY-MM-DDTHH:MM:SSZ. If the property is not present, consumers can assume that the object has not been modified since the published date.
image","text":"The image property provides a notable or representative image for the text. It can be included by reference as an id or with an Image type object.
There can be multiple values for the image property, either as an array of id strings or Image objects. Publishers should provide these in order of importance, with the most important image first. Consumers can use as many or as few as needed.
content","text":"The full text of the article or blog post should be in the content property. This should be HTML. Using mediaType to set a different media type presumes that consumers will be able to display that type.
The HTML elements in the content property should include a sanitized subset of the full HTML element set. It should not include any CSS or JavaScript. This subset should include:
<p><span> (class)<h2>, <h3>, <h4>, <h5>, <h6><br><a> (href, rel, class)<del><pre><code><em><strong><b><i><u><ul><ol> (start, reversed)<li> (value)<blockquote><img> (src, alt, title, width, height, class)<video> (src, controls, loop, poster, width, height, class)<audio> (src, controls, loop, class)<source> (src, type)<ruby><rt><rp>The HTML should only include the content of the text. Additional navigation to other pages on the originating site, like category links or home page links, should not be included. Other affordances like \"favourite\", \"like\", \"bookmark\" or other buttons should not be included. It should not include a \"Read more...\" link to the full article.
Any embedded media like images, video or audio in the content property should also be listed in the attachment property so that consumers can pre-fetch the media.
source","text":"If the text was originally created in a different format, the original source should be in the source property to allow editing the content. It should include the mediaType of the source format and the content property with the original content.
replies","text":"Comments on the text should be linked in the replies property. This should be a URL that resolves to a collection of objects for the replies.
Comments are usually Note objects, but can be other types of objects like Article or Question.
This property provides the same functionality as the comments property in RSS 2.0.
inReplyTo","text":"If the text is a reply to another ActivityPub object, such as an Article or Note, the inReplyTo property can include a string with the URL of the object being replied to, or a JSON object representing that object.
If the text is a commentary on or review of a particular link on the Web, the inReplyTo property can include a Link object with a href property that is the URL of the linked resource.
attachment","text":"The attachment property provides additional media that is part of the text. This can include images, video, audio, or other media. Consumers can use this property to pre-fetch media for display without needing to load and parse the full content property.
tag","text":"The tag property provides additional metadata about the text. There are two important types of tags:
name property with the tag text.href property with the URL of the actor's profile page.context","text":"If the text is part of a larger collection, the context property can provide a link to the collection. An example might be an article in a series, a newspaper column, a blog category (although tag may be better here) or a section of a magazine.
There can be multiple context properties, either as an array of strings or objects or both.
generator","text":"The generator property provides information about the software that generated the text. This is usually an Application or Service object with an id and a name property.
preview","text":"In AS2, the preview property provides an abbreviated version of the content of the object. Especially for microblogging applications, the preview property is a useful fallback for supporting unrecognized object types like Article.
For an article, the preview can be a Note that gives a well-formatted preview of the article content in its content property. For example, the name and summary. The preview content SHOULD NOT include a link to the HTML representation for the article. Additional navigation to other pages on the originating site, like category links, home page links, and other affordances like \"favourite\", \"like\", \"bookmark\" or other buttons should not be included.
The content property of the preview should include a minimal set of HTML elements, as described in ActivityPub Primer HTML.
Metadata on the Article that applies equally to the preview, such as attributedTo, published, updated, and tag can be repeated in the preview property. The consumer should fall back to the Article properties if they are not present in the preview.
The image property of the Article may be included in the preview property as attachment items.
The preview property may have an id property.
to, cc, bcc, bto, audience","text":"As with other AS2 object types, the to, cc, bcc, bto, and audience properties identify the addressees of the text. For ActivityPub, they also determine the delivery targets of the text.
The addressing properties provide an access control mechanism for AS2. Publishers and consumers should not disclose the properties of any AS2 object type, including the Article type, with anyone except the addressees, listed in these addressing properties, or the creator(s), listed in the attributedTo property.
sensitive","text":"sensitive marks an article as potentially sensitive, controversial, or disturbing in the author's opinion. As a non-exhaustive list and depending on context, nudity, sexual activity, violence, or spoilers for a movie or book may be considered sensitive.
If the sensitive flag is set, the consumer should obscure the content of the article until the user conveys intent to read the article or view embedded media.
To help the user decide whether to read the article or view its media, the consumer should show these properties, if provided, in order:
dcterms:subject: a property from the Dublin Core metadata terms. Value should be a string or array of strings indicating the topic or topics discussed in the article.tag: in particular, Hashtag names. (subject is preferred because hashtags are often less human-readable).name: as described above, the title of the Article. Authors are more likely to leak sensitive material in the title, so the subject and/or hashtags should be used first.summary: as described above. Only as a last resort when other properties are not defined; a well-written summary is likely to include significant excerpts or summation of the sensitive content.This section includes examples of long-form text objects. Note that for brevity, the content is not actually multi-paragraph text.
"},{"location":"fep/b2b8/#long-form-text-with-included-content","title":"Long-form text with included content","text":"{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.com/2024/11/07/long-form-text.jsonld\",\n \"name\": \"Long-form text with included content\",\n \"url\": \"https://example.com/2024/11/07/long-form-text.html\",\n \"attributedTo\": \"https://example.com/evan\",\n \"summary\": \"<p>This is a long-form text object with included content. It has a title, a summary, and a full text.</p>\",\n \"content\": \"<p>This is a long-form text object with included content. It has a title, a summary, and a full text.</p>\",\n \"published\": \"2024-11-07T12:00:00Z\"\n}\n"},{"location":"fep/b2b8/#long-form-text-with-external-content","title":"Long-form text with external content","text":"{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.com/2024/11/07/long-form-text-no-content.jsonld\",\n \"name\": \"Long-form text with included content\",\n \"url\": \"https://example.com/2024/11/07/long-form-text-no-content.html\",\n \"attributedTo\": \"https://example.com/evan\",\n \"summary\": \"<p>This is a long-form text object with external content. It has a title, a summary, and a link to the full text.</p>\"\n \"published\": \"2024-11-07T12:00:00Z\"\n}\n"},{"location":"fep/b2b8/#long-form-text-with-full-author-information","title":"Long-form text with full author information","text":"{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.com/2024/11/07/long-form-text-author.jsonld\",\n \"name\": \"Long-form text with full author information\",\n \"url\": \"https://example.com/2024/11/07/long-form-text-author.html\",\n \"attributedTo\": {\n \"type\": \"Person\",\n \"id\": \"https://example.com/evan\",\n \"name\": \"Evan Prodromou\",\n \"summary\": \"<p>Founder of Social Web Foundation</p>\",\n \"url\": \"https://example.com/evan\",\n \"icon\": {\n \"type\": \"Image\",\n \"mediaType\": \"image/png\",\n \"url\": \"https://example.com/evan.png\"\n }\n },\n \"summary\": \"<p>This is a long-form text object with full author information. It has a title, a summary, and an URL to the full text.</p>\"\n \"published\": \"2024-11-07T12:00:00Z\"\n}\n"},{"location":"fep/b2b8/#long-form-text-with-embedded-images","title":"Long-form text with embedded images","text":"{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.com/2024/11/07/long-form-text-images.jsonld\",\n \"name\": \"Long-form text with embedded images\",\n \"url\": \"https://example.com/2024/11/07/long-form-text-images.html\",\n \"attributedTo\": \"https://example.com/evan\",\n \"summary\": \"<p>This is a long-form text object with embedded images.</p>\",\n \"content\": \"<p>This is a long-form text object with embedded images.</p><img src=\\\"https://example.com/image1.jpg\\\" alt=\\\"Image 1\\\"><img src=\\\"https://example.com/image2.jpg\\\" alt=\\\"Image 2\\\">\",\n \"attachment\": [\n {\n \"type\": \"Image\",\n \"id\": \"https://example.com/image1.jpg\",\n \"mediaType\": \"image/jpeg\"\n },\n {\n \"type\": \"Image\",\n \"id\": \"https://example.com/image2.jpg\",\n \"mediaType\": \"image/jpeg\"\n }\n ],\n \"published\": \"2024-11-07T12:00:00Z\"\n}\n"},{"location":"fep/b2b8/#long-form-text-with-tags","title":"Long-form text with tags","text":"{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.com/2024/11/07/long-form-text-tags.jsonld\",\n \"name\": \"Long-form text with tags\",\n \"url\": \"https://example.com/2024/11/07/long-form-text-tags.html\",\n \"attributedTo\": \"https://example.com/evan\",\n \"summary\": \"<p>This is a long-form text object with tags.</p>\",\n \"content\": \"<p>@<a href='https://example.com/evan'>evan</a> made this #<a href='https://example.com/tag/example'>example</a>.</p>\",\n \"tag\": [\n {\n \"type\": \"Hashtag\",\n \"name\": \"example\",\n \"href\": \"https://example.com/tag/example\"\n },\n {\n \"type\": \"Mention\",\n \"href\": \"https://example.com/evan\"\n }\n ],\n \"published\": \"2024-11-07T12:00:00Z\"\n}\n"},{"location":"fep/b2b8/#long-form-text-with-context","title":"Long-form text with context","text":"{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.com/2024/11/07/long-form-text-context.jsonld\",\n \"name\": \"Long-form text with context\",\n \"url\": \"https://example.com/2024/11/07/long-form-text-context.html\",\n \"attributedTo\": \"https://example.com/evan\",\n \"summary\": \"<p>This is a long-form text object with context.</p>\",\n \"content\": \"<p>This is a long-form text object with context.</p>\",\n \"context\": [\n \"https://example.com/2024/11/07/series\",\n \"https://example.com/2024/11/07/category\"\n ],\n \"published\": \"2024-11-07T12:00:00Z\"\n}\n"},{"location":"fep/b2b8/#long-form-text-with-preview","title":"Long-form text with preview","text":"{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Article\",\n \"id\": \"https://example.com/2025/02/17/long-form-text-preview.jsonld\",\n \"name\": \"Long-form text with preview\",\n \"url\": \"https://example.com/2025/02/17/long-form-text-preview.html\",\n \"attributedTo\": \"https://example.com/evan\",\n \"summary\": \"<p>This is the summary for a long-form text with a preview.</p>\",\n \"content\": \"<p>This is the content for a long-form text with a preview.</p>\",\n \"published\": \"2024-11-07T12:00:00Z\",\n \"image\": {\n \"type\": \"Link\",\n \"href\": \"https://example.com/image.jpg\",\n \"mediaType\": \"image/jpeg\"\n },\n \"preview\": {\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/evan\",\n \"content\": \"<p><strong>Long-form text with preview</strong></p><p>This is the summary for a long-form text with a preview.</p>\",\n \"published\": \"2024-11-07T12:00:00Z\",\n \"attachment\": {\n \"type\": \"Link\",\n \"href\": \"https://example.com/image.jpg\",\n \"mediaType\": \"image/jpeg\"\n }\n }\n}\n"},{"location":"fep/b2b8/#long-form-text-with-senstive-content","title":"Long-form text with senstive content","text":"This article includes a spoiler about the 1941 film Citizen Kane. The sensitive property is set to true, and the dcterms:subject property is used to indicate the topic of the article.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/miscellany\",\n {\"dcterms\": \"http://purl.org/dc/terms/\"}\n ],\n \"id\": \"https://example.com/article/1\",\n \"type\": \"Article\",\n \"name\": \"Spoiler for Citizen Kane\",\n \"summary\": \"<p>I am going to tell you what Rosebud was.</p>\",\n \"sensitive\": true,\n \"dcterms:subject\": [\"Citizen Kane\"],\n \"contents\": \"<p>Rosebud was his sled!</p>\",\n \"tag\": {\n \"id\": \"https://example.com/tag/citizenkane\",\n \"name\": \"citizenkane\",\n \"type\": \"Hashtag\"\n }\n}\n"},{"location":"fep/b2b8/#user-interface-guidance","title":"User interface guidance","text":"Consumers should use their native interfaces to handle Article objects in an intuitive way that integrates well with other object types. The following illustrations provide examples of how Article objects might be displayed in a stream-oriented social web interface, such as a microblogging application. The UI elements are labelled with the properties of the Article object that most likely correspond to them.
An example of a long-form text object with an image property displayed in a social stream.
An example of a long-form text object without an image property displayed in a social stream.
An example of a long-form text object without a name property displayed in a social stream.
An example of a long-form text object with a sensitive property displayed in a social stream with a content warning.
An example of a long-form text object with a preview property displayed in a social stream. Note that the consumer is responsible for displaying a link to the article's full content.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/bad1/","title":"Index","text":"hide: - navigation description: \"[AS2-Core] provides examples 18, 19, 32 which represent the \"history\" of an object. Particularly in example 32, we see an object being Created, Updated, and Deleted. However, there is no property dedicated to advertising a collection fit for this purpose. This FEP attempts to define one.\"
"},{"location":"fep/bad1/#fep-bad1-object-history-collection","title":"FEP-bad1: Object history collection","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT 2023-06-15 #124 - codeberg"},{"location":"fep/bad1/#summary","title":"Summary","text":"[AS2-Core] provides examples 18, 19, 32 which represent the \"history\" of an object.
Particularly in example 32, we see an object being Created, Updated, and Deleted. However, there is no property dedicated to advertising a collection fit for this purpose. This FEP attempts to define one.
"},{"location":"fep/bad1/#examples-taken-from-activitystreams-20","title":"Examples taken from ActivityStreams 2.0","text":"(This section is non-normative.)
For convenience, the following examples are reproduced from the ActivityStreams 2.0 [AS2-Core] specification. Examples 18 and 19 are found in normative sections; example 32 is found in a non-normative section.
Example 18:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"summary\": \"Object history\",\n \"type\": \"Collection\",\n \"totalItems\": 2,\n \"items\": [\n {\n \"type\": \"Create\",\n \"actor\": \"http://www.test.example/sally\",\n \"object\": \"http://example.org/foo\"\n },\n {\n \"type\": \"Like\",\n \"actor\": \"http://www.test.example/joe\",\n \"object\": \"http://example.org/foo\"\n }\n ]\n}\n Example 19:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"summary\": \"Object history\",\n \"type\": \"OrderedCollection\",\n \"totalItems\": 2,\n \"orderedItems\": [\n {\n \"type\": \"Create\",\n \"actor\": \"http://www.test.example/sally\",\n \"object\": \"http://example.org/foo\"\n },\n {\n \"type\": \"Like\",\n \"actor\": \"http://www.test.example/joe\",\n \"object\": \"http://example.org/foo\"\n }\n ]\n}\n Example 32:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"oa\": \"http://www.w3.org/ns/oa#\",\n \"prov\": \"http://www.w3.org/ns/prov#\",\n \"dcterms\": \"http://purl.org/dc/terms/\",\n \"dcterms:created\": {\n \"@id\": \"dcterms:created\",\n \"@type\": \"xsd:dateTime\"\n }\n }\n ],\n \"summary\": \"Editing history of a note\",\n \"type\": \"Collection\",\n \"items\": [\n {\n \"id\": \"http://example.org/activity/20150101000000\",\n \"type\": [ \"Create\", \"prov:Activity\" ],\n \"actor\": {\n \"id\": \"http://example.org/#eric\",\n \"name\": \"Eric\"\n },\n \"summary\": \"Eric wrote a note.\",\n \"object\": {\n \"id\": \"http://example.org/entry/20150101000000\",\n \"type\": [ \"Note\", \"prov:Entity\" ],\n \"attributedTo\": \"http://example.org/#eric\",\n \"content\": \"Remember... all I'm offering is the trooth. Nothing more.\"\n },\n \"published\": \"2015-01-01T00:00:00Z\"\n },\n {\n \"id\": \"http://example.org/activity/20150101000059\",\n \"type\": [ \"Update\", \"prov:Activity\", \"oa:Annotation\" ],\n \"summary\": \"Eric edited a note.\",\n \"dcterms:created\": \"2015-01-01T00:00:59Z\",\n \"dcterms:creator\": { \"@id\": \"http://example.org/#eric\" },\n \"oa:hasBody\": {\n \"id\": \"http://example.org/entry/20150101000059\",\n \"type\": [ \"Note\", \"prov:Entity\" ],\n \"content\": \"Remember... all I'm offering is the truth. Nothing more.\",\n \"prov:wasAttributedTo\": { \"@id\": \"http://example.org/#eric\" },\n \"prov:wasRevisionOf\": { \"@id\": \"http://example.org/entry/20150101000000\" }\n },\n \"oa:hasTarget\": { \"@id\": \"http://example.org/entry/20150101000000\" },\n \"oa:motivatedBy\": { \"@id\": \"oa:editing\" },\n \"prov:generated\": { \"@id\": \"http://example.org/entry/20150101000059\" },\n \"prov:wasInformedBy\": { \"@id\": \"http://example.org/activity/20150101000000\" }\n },\n {\n \"id\": \"http://example.org/activity/20150101010101\",\n \"type\": [ \"Delete\", \"prov:Activity\" ],\n \"actor\": \"http://example.org/#eric\",\n \"summary\": \"Eric deleted a note.\",\n \"object\": \"http://example.org/entry/20150101000059\",\n \"published\": \"2015-01-01T01:01:01Z\"\n }\n ]\n}\n From these examples, Example 18 is nearly identical to Example 19, except for the type being Collection in Example 18, and OrderedCollection in Example 19. An object's history collection will necessarily be ordered chronologically, although whether the ordering should be forward chronological or reverse chronological is an open question; at the time of writing this FEP, [ActivityPub] Section 5 currently contains the following language:
An OrderedCollection MUST be presented consistently in reverse chronological order.
This language indicates that if OrderedCollection is used, the ordering MUST be reverse chronological.
history special collection","text":"An object's history is discovered through the history property of an object. The history MUST be an OrderedCollection.
The history stream contains all activities which target the object as object, where the actor matches the attributedTo actor. This might include Create, Update, and/or Delete activities.
This is differentiated from context, which per FEP-7888 may be a collection containing related objects and activities. It is possible in certain cases to obtain an object's history by filtering such a collection for all items that contain an object referencing a given object, but this is not a consistent or straightforward way of doing so.
In the associated context, we define the following terms:
"},{"location":"fep/bad1/#history","title":"history","text":"URI : https://w3id.org/fep/bad1/history
Notes : A collection containing all activities performed by the author related to this object's representation.
JSON-LD definition (@id pending adoption of FEP-9606):
{\n \"@context\": {\n \"history\": {\n \"@id\": \"https://w3id.org/fep/bad1/history\",\n \"@type\": \"@id\"\n }\n }\n}\n Example:
{\n \"@context\": [\n \"https://w3id.org/fep/bad1/history\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"id\": \"https://example.com/some-file\",\n \"type\": \"Tombstone\",\n \"formerType\": \"Document\",\n \"url\": \"https://example.com/404\",\n \"history\": {\n \"id\": \"https://example.com/some-object/log\",\n \"type\": \"OrderedCollection\",\n \"orderedItems\": [\n {\n \"id\": \"https://example.com/some-file/log/3\",\n \"type\": \"Delete\",\n \"object\": \"https://example.com/some-file\"\n },\n {\n \"id\": \"https://example.com/some-file/log/2\",\n \"type\": \"Update\",\n \"object\": {\n \"id\": \"https://example.com/some-file\",\n \"url\": \"https://example.com/storage/hash2\"\n }\n },\n {\n \"id\": \"https://example.com/some-file/log/1\",\n \"type\": \"Create\",\n \"object\": {\n \"type\": \"Document\",\n \"url\": \"https://example.com/storage/hash1\"\n }\n }\n ]\n }\n}\n"},{"location":"fep/bad1/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c0e0/","title":"FEP-c0e0: Emoji reactions","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2024-08-08 #384 Discussions codeberg"},{"location":"fep/c0e0/#summary","title":"Summary","text":"This document describes how emoji reactions are implemented in ActivityPub network.
"},{"location":"fep/c0e0/#history","title":"History","text":"Misskey supports emoji reactions since version 10.97.0 (2019). Pleroma supports emoji reactions since version 2.0.0 (2020).
"},{"location":"fep/c0e0/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/c0e0/#emojireact-activity","title":"EmojiReact activity","text":"EmojiReact activity type is considered to be a part of LitePub vocabulary. Its full IRI is http://litepub.social/ns#EmojiReact.
This activity is similar to Like activity. In addition to standard properties of Like activity, EmojiReact activity MUST have a content property. Reaction content MUST be either a single unicode grapheme, or a name of a custom emoji enclosed in colons (shortcode).
If custom emoji is used, EmojiReact activity MUST have a tag property containing a single Emoji object. The value of its name property MUST contain the name of the custom emoji, and it SHOULD be enclosed in colons. The embedded Emoji can originate from a server that is different from the actor's server.
An actor can generate multiple EmojiReact activities for a single object. However, implementers MAY choose to not allow more than one reaction with the same emoji, or more than one reaction per object.
Example with unicode emoji:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"litepub\": \"http://litepub.social/ns#\",\n \"EmojiReact\": \"litepub:EmojiReact\"\n }\n ],\n \"actor\": \"https://alice.social/users/alice\",\n \"content\": \"\ud83d\udd25\",\n \"id\": \"https://alice.social/activities/65379d47-b7aa-4ef6-8e4f-41149dda1d2c\",\n \"object\": \"https://bob.social/objects/57caeb99-424c-4692-b74f-0a6682050932\",\n \"to\": [\n \"https://alice.social/users/alice/followers\",\n \"https://bob.social/users/bob\"\n ],\n \"type\": \"EmojiReact\"\n}\n Example with custom emoji:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"toot\": \"http://joinmastodon.org/ns#\",\n \"Emoji\": \"toot:Emoji\",\n \"litepub\": \"http://litepub.social/ns#\",\n \"EmojiReact\": \"litepub:EmojiReact\"\n }\n ],\n \"actor\": \"https://alice.social/users/alice\",\n \"content\": \":blobwtfnotlikethis:\",\n \"id\": \"https://alice.social/activities/65379d47-b7aa-4ef6-8e4f-41149dda1d2c\",\n \"object\": \"https://bob.social/objects/57caeb99-424c-4692-b74f-0a6682050932\",\n \"tag\": [\n {\n \"icon\": {\n \"mediaType\": \"image/png\",\n \"type\": \"Image\",\n \"url\": \"https://alice.social/files/1b0510f2-1fb4-43f5-a399-10053bbd8f0f\"\n },\n \"id\": \"https://alice.social/emojis/blobwtfnotlikethis\",\n \"name\": \":blobwtfnotlikethis:\",\n \"type\": \"Emoji\",\n \"updated\": \"2024-02-07T02:21:46.497Z\"\n }\n ],\n \"to\": [\n \"https://alice.social/users/alice/followers\",\n \"https://bob.social/users/bob\"\n ],\n \"type\": \"EmojiReact\"\n}\n"},{"location":"fep/c0e0/#like-with-content","title":"Like with content","text":"Emoji reaction can also be represented as a Like activity. This variant of emoji reaction will processed by non-supporting implementations as a regular \"like\", and when that is preferable, implementers MAY use Like type instead of EmojiReact type.
Implementations MUST process Like with content in the same way as EmojiReact activities.
Emoji reactions can be retracted using a standard Undo activity:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"actor\": \"https://alice.social/users/alice\",\n \"id\": \"https://alice.social/activities/99b8f47b-f3a9-4cf5-94a2-95352e7462d6\",\n \"object\": \"https://alice.social/activities/65379d47-b7aa-4ef6-8e4f-41149dda1d2c\",\n \"to\": [\n \"https://alice.social/users/alice/followers\",\n \"https://bob.social/users/bob\"\n ],\n \"type\": \"Undo\"\n}\n"},{"location":"fep/c0e0/#emojireactions-collection","title":"emojiReactions collection","text":"The list of emoji reactions to an object can be exposed using the emojiReactions property. Its full IRI is http://fedibird.com/ns#emojiReactions.
The URL specified by this property MUST resolve to a collection containing Like (with content) and EmojiReact activities.
This document is based on implementations of emoji reactions in Misskey, Pleroma and Fedibird.
A number of other projects announced support for this FEP after it was published:
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c118/","title":"FEP-c118: Content licensing support","text":"Authors Status Date received Tracking issue Discussions Repository Tim Bray tbray@texgtuality.comDRAFT 2023-01-16 #57 - codeberg"},{"location":"fep/c118/#summary","title":"Summary","text":"Currently, popular Fediverse software does very little to establish the legal status of posts. Controversy over indexing and scraping the Fediverse is common. The hope is that providing a legal framework to express the desires of users as to how their content may be re-used might bring order to this debate.
"},{"location":"fep/c118/#scenario","title":"Scenario","text":"Dramatis Personae: * A somewhat-technical new Fediverse arrival, hereinafter Noob * Existing Fediverse Voices, hereinafer EFV1, EFV2, etc
Noob: Hey, I want to crawl the Fediverse and build an index so we can\u2026
EFV1: Stop! Go back! Don't!
EFV2: We prefer not to do that here because we want to avoid into big-tech surveillance capitalism.
Noob: But I mean they're all just URLs, right?
EFV3: Burn the witch!
EFV4: Unclean!
Noob: Oh, I guess you don't want me to do this?
EFV5: Never!
Noob: Shouldn't you post the rules somewhere? I mean, lots of people are already doing this.
EFV6: Defederate his instance!
"},{"location":"fep/c118/#discussion","title":"Discussion","text":"While Fediverse search has become less controversial since the previous revision of this FEP, the issue remains highly relevant with the advent of crawlers from AI companies gathering material to feed into LLMs.
At the moment, the Fediverse privacy story is unsatisfactory. Unless a user really understands the visibility levels, every post they make instantly becomes part of the permanent indelible Internet memory. There are no technical and very few legal barriers to prevent anyone from doing anything with retrieved post content.
In developed societies, when there are behaviors that are logistically possible but society would like to regulate, legal tools are frequently applied. Examples would be dangerous driving and public smoking.
A content-licensing framework for the Fediverse could include some or all of the following:
I note that this document template comes with a carefully-considered Creative Commons license. Do not the contributions of the humans who are enriching the Fediverse deserve a comparable level of protection?
"},{"location":"fep/c118/#caveat","title":"Caveat","text":"Note that establishing a legal content-licensing framework will not prevent certain bad actors from scraping the Fediverse and mis-using the harvested data. That's OK because it will establish a legal framework that will present a significant barrier to commercial data harvesters, and provide a tool to combat certain classes of mis-use.
"},{"location":"fep/c118/#see-also","title":"See also:","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c16b/","title":"FEP-c16b: Formatting MFM functions","text":"Authors Status Date received Tracking issue Discussions Repository ilja ilja@ilja.spaceDRAFT 2024-08-10 #383 Discussions codeberg"},{"location":"fep/c16b/#summary","title":"Summary","text":"This FEP recommends a method for formatting MFM in ActivityPub post content using HTML with custom classes and [data-* attributes]. Furthermore, this FEP provides a new extension term to indicate that this HTML representation is used.
"},{"location":"fep/c16b/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in [RFC-2119]. \u201cFediverse implementation\u201d or \"implementation\u201d is to be interpreted as an ActivityPub conformant Client, ActivityPub conformant Server or ActivityPub conformant Federated Server as described in [ActivityPub].
"},{"location":"fep/c16b/#acknowledgements","title":"Acknowledgements","text":"(This section is non-normative.)
The core idea behind this FEP is attributed to Johan150 on the Foundkey issue tracker[1]. Specifically, the proposal for representing the MFM functions in HTML using span elements with custom classes and data-* attributes.
(This section is non-normative.)
It is common for a Fediverse implementation to allow a markup language as input for text. Federation of this content generally happens by converting this text input to a proper HTML representation that another implementation can easily understand. This HTML representation is federated over ActivityPub using the content property of the [ActivityStreams] Object. Meanwhile, the source property, which was added by ActivityPub, can optionally be used to provide the original input and input format.
Misskey has been using its own [Markup language For Misskey], also known as MFM. MFM is mostly composed of a combination of HTML, Markdown, Katex, and custom MFM functions of the form $[name content]. Properly displaying what these MFM functions intent, generally requires complex CSS or even Javascript. As such, only a watered down HTML representation is provided in the content. This representation can strip out so much information that a receiving implementation cannot always properly display what the author meant to convey. The only option for a receiving implementation who wants to display the MFM correctly, is to re-parse the content of the source property when it's mediaType has value text/x.misskeymarkdown. This causes not only unnecessary overhead, but also compatibility issues, especially when two implementations are using a different parser.
(This section is non-normative.)
An MFM Function consists of a name, optionally one or more attributes who may or may not have a value, and a content. It has the form $[name.attribute1,attribute2=value content].
(This section is non-normative.)
$[x2 Misskey expands the world of the Fediverse]\n$[jelly.speed=2s Misskey expands the world of the Fediverse]\n$[spin.x,speed=0.5s Misskey expands the world of the Fediverse]\n"},{"location":"fep/c16b/#html-representation-of-mfm-functions","title":"HTML representation of MFM functions","text":"When representing the MFM function in HTML, a span element MUST be used. The span element MUST have a class mfm-name where name is the name of the MFM function. When the MFM function has attributes, the span element MUST have a data-* attribute data-mfm-attributename for each attribute, where attributename is the name of the attribute in question. If the attribute of the MFM function has a value, the data-* attribute MUST have the same value.
(This section is non-normative.)
This turns the previous examples into
<span class=\"mfm-x2\">Misskey expands the world of the Fediverse</span>\n<span class=\"mfm-jelly\" data-mfm-speed=\"2s\">Misskey expands the world of the Fediverse</span>\n<span class=\"mfm-flip\" data-mfm-x data-mfm-speed=\"0.5s\">Misskey expands the world of the Fediverse</span>\n"},{"location":"fep/c16b/#other-mfm-components","title":"Other MFM components","text":"While this FEP focuses on the representation of MFM functions, MFM consist of more than just these MFM functions. The HTML representation in the content property MUST be correct and complete such that a receiving implementation can use it to correctly display what the MFM conveys.
HTML and Markdown are generally expressed correctly in the content property, and are both widely used on the Fediverse. Therefor these are not considered problematic in the same sense as the MFM functions are.
Katex suffers the same problem of generally not being expressed properly in the content property. For properly expressing the Katex input as HTML, [FEP-dc88] SHOULD be used.
(This section is non-normative.)
When compatibility with an MFM capable, but FEP-c16b non-compliant, implementation is desired, the source may still need to be federated using \"mediaType\": \"text/x.misskeymarkdown\". Meanwhile, the incoming source from this implementation may still need to be re-parsed. As such, a discovery mechanism is required to signal to a FEP-c16b compliant implementation that the content may be used directly.
For this purpose, a new extension term is proposed, as described in [FEP-888d].
"},{"location":"fep/c16b/#htmlmfm","title":"htmlMfm","text":"To signal that the content is FEP-c16b compliant, the implementation MAY use the extension term htmlMfm with value true. When the content is not FEP-c16b compliant, the implementation MUST NOT use the extension term htmlMfm with value true, but MAY use the extension term htmlMfm with value false.
content is FEP-c16b compliant.https://w3id.org/fep/c16b#htmlMfmhttps://www.w3.org/ns/activitystreams#Object(This section is non-normative.)
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"htmlMfm\": \"https://w3id.org/fep/c16b#htmlMfm\"\n }\n ],\n \"content\": \"<span class=\\\"mfm-spin\\\" data-mfm-x data-mfm-speed=\\\"0.5s\\\">Misskey expands the world of the Fediverse</span>\",\n \"source\": {\n \"content\": \"$[spin.x,speed=0.5s Misskey expands the world of the Fediverse]\",\n \"mediaType\": \"text/x.misskeymarkdown\"\n },\n \"htmlMfm\": true\n}\n"},{"location":"fep/c16b/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c180/","title":"FEP-c180: Problem Details for ActivityPub","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.orgDRAFT 2025-03-11 #531 Discussions codeberg"},{"location":"fep/c180/#summary","title":"Summary","text":"ActivityPub is a RESTful API and HTTP-based protocol for standards-based social networking, but does not specify an error format. This document provides a profile of the Problem Details for HTTP APIs specification (RFC 9457) for use with ActivityPub.
"},{"location":"fep/c180/#introduction","title":"Introduction","text":"ActivityPub is the W3C standard for federated social networking. It describes a standard RESTful API for social applications that allows people to create and share social content like text, images, audio and video, as well as reacting to social content and building a social graph of connections between people. ActivityPub also includes a standard protocol for federating social content between servers, so that people on different social platforms can interact with each other.
Both the client API and the server-to-server protocol are based on HTTP, and use HTTP status codes to indicate the success or failure of requests. However, HTTP status codes are not always sufficient to describe the nature of an error, or to provide enough information for a client to recover from an error.
The Problem Details for HTTP APIs specification (RFC 9457) describes a way to provide more detailed information about errors in an HTTP response. The format includes a machine-readable description of the error, as well as a human-readable explanation, additional data about the error, and a link to more information about the error.
This document describes a number of specific error types that are relevant to ActivityPub, and provides guidance on how to use the Problem Details for HTTP APIs format with ActivityPub.
"},{"location":"fep/c180/#motivating-use-cases","title":"Motivating use cases","text":"ActivityPub servers SHOULD use the Problem Details for HTTP APIs format to describe errors in responses to HTTP requests. The format is described in RFC 9457.
These types of HTTP request in the ActivityPub API and federation protocol SHOULD use the Problem Details format for errors (abbreviations used in this document are in parentheses):
Other ActivityPub requests MAY use the Problem Details format.
The about:blank type defined in RFC 9457 MAY be used for problems that do not have a specific type. Other types registered in the IANA Problem Type Registry MAY be used for specific problems.
Problem types in this vocabulary use the https://w3id.org/fep/c180 prefix.
Each of the following problem types lists the applicability of the problem (per the list of request types above), the type URI, the title of the problem, the HTTP status code that SHOULD be used, and additional fields that MAY be included in the response.
"},{"location":"fep/c180/#unsupported-type","title":"Unsupported type","text":"id of the object with the unsupported typetype that is not supportedThis indicates that the type of the activity, or one of the objects referred to by the activity, is not supported by the API server or the receiving federation protocol server.
"},{"location":"fep/c180/#object-does-not-exist","title":"Object does not exist","text":"id of the object that does not existThe activity refers to an object in one of its properties, such as object, target, or an addressing property, but the object does not exist. Recursively connected objects, like the inReplyTo property of the object property, can also be checked.
Note that this type is distinct from an endpoint returning a 404 Not Found status code for a GET request for an object that does not exist, or for posting to an endpoint that does not exist.
"},{"location":"fep/c180/#duplicate-delivery","title":"Duplicate delivery","text":"id of the activity that was previously deliveredThe activity has already been delivered to the inbox or to all accounts using the sharedInbox.
Note that this is different from Redundant activity. Duplicate delivery is when the same activity is delivered multiple times. Redundant activity is when two different activities that do the same thing are received.
"},{"location":"fep/c180/#redundant-activity","title":"Redundant activity","text":"id of the previous activityThe activity is a duplicate of a previous activity which has already been processed by the server, and which has not been reverted with an Undo activity or with other activities. The duplicate property contains the id of the previous activity.
Activity types that are often treated as idempotent and can only be processed once include Create, Delete, Follow, Accept, Reject, Add, Remove, Block, Undo, and Like. Other activity types like Announce are treated as idempotent by some servers.
Note that this is different from Duplicate delivery.Redundant activity is when two different activities that do the same thing are received. Duplicate delivery is when the same activity is delivered multiple times.
"},{"location":"fep/c180/#approval-required","title":"Approval required","text":"id of the actor who must approve the activityThe activity will be delivered to the addressees, but may not have side effects applied until it is approved by an administrator, moderator, or one of the addressees.
For example, a Follow activity may be delivered to the addressee's inbox, but the Accept activity may not be returned until the addressee approves the follow request.
As another example, a Create activity with an object property with an inReplyTo property may require approval by the author of the replied-to object before it is added to that object's replies collection.
This problem type would be used for activities that are manually approved, not automatically approved.
"},{"location":"fep/c180/#not-an-actor","title":"Not an actor","text":"id of the object that is not an actorThe activity refers to an object in one of its properties, such as object or an addressing property, that requires an ActivityPub actor to be correctly processed, but the object is not an actor.
id of the principalid of the actorThe security principal of the request, such as the authenticated user, does not match the actor that is the subject of the activity.
For example, the authenticated user is trying to send a Follow activity to another actor, but the actor property of the activity is not the authenticated user.
Note that it is possibly valid for the actor property of an activity to not be the same as the authenticated user; for example, with inbox forwarding.
id of the actorid of the resource the actor is unauthorized to accessThe actor is not authorized to perform the given activity on, to, or from a given object.
For example, with an Add activity, the actor is not authorized to add the object to the target collection.
As another example, with a Delete activity, the actor is not authorized to delete the object.
Another example would be a Like activity for an object where the actor has been blocked by the creator of the object.
Note that this type is distinct from Principal not authorized, which indicates that the authenticated user is not authorized to perform the activity.
"},{"location":"fep/c180/#principal-not-authorized","title":"Principal not authorized","text":"id of the principalid of the resource the principal is unauthorized to accessThis problem type indicates that the security principal, such as the authenticated user, is not authorized to perform the given activity on, to, or from a given object.
It can also be used to indicate that the authenticated user is not authorized to GET an object, either directly or through a proxy.
This type is distinct from actor not authorized. This type should only be used when the principal and the actor are distinct, or when there is no actor (such as with GET requests).
"},{"location":"fep/c180/#client-not-authorized","title":"Client not authorized","text":"id of the clientThis problem type is applicability GET and POST requests.
This indicates that the client is not authorized to perform the given activity. The security principal, like the authenticated user, may be authorized, but the client is not.
An example would be a client that uses OAuth 2.0 to authenticate, perhaps with FEP-d8c2, but has not been granted the proper scopes to perform the activity.
This error type implies, but does not promise, that the security principal would be authorized to perform the activity with a different client.
This problem type is primarily for the ActivityPub API, between a client and a server. In the case of the federation protocol, where the principal is closely tied to the platform that is sending the activity, there may not be a meaningful way for the principal to interact without the client (in this case, their server).
"},{"location":"fep/c180/#unsupported-media-type","title":"Unsupported media type","text":"The media type of the uploaded file is not supported by the server.
"},{"location":"fep/c180/#media-too-large","title":"Media too large","text":"The uploaded file is too large to be processed by the server.
"},{"location":"fep/c180/#no-applicable-addressees","title":"No applicable addressees","text":"The activity does not have any addressees that are applicable to the server. This could be because the activity has no to, cc, or bcc properties, or because the addressees do not have inboxes on the server.
Another case is where the addressees are Collections, and no actor in the collection has an inbox on the server. For example, if an activity is addressed to the actor's followers collection, but none of the followers have inboxes on the server.
"},{"location":"fep/c180/#rate-limit-exceeded","title":"Rate limit exceeded","text":"The client or the security principal has exceeded the rate limit for the given activity. The server MAY include a Retry-After header in the response to indicate when the rate limit will be reset.
This problem type is primarily applicable to the ActivityPub API, between a client and a server. It is unusual for a server to rate limit incoming activities over the federation protocol.
"},{"location":"fep/c180/#privacy-considerations","title":"Privacy considerations","text":"Some of the problem types in this document may reveal information about the server's internal state, such as the existence of an object, the relationship of an object to an actor, or a relationship between actors. Servers should be careful to avoid revealing sensitive information in error messages.
"},{"location":"fep/c180/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c390/","title":"FEP-c390: Identity Proofs","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2022-11-23 #34 Discussions codeberg"},{"location":"fep/c390/#summary","title":"Summary","text":"This proposal describes a mechanism of creating verifiable links between Decentralized Identifiers and ActivityPub actor profiles.
Potential applications include: identity verification, end-to-end encryption and account migrations.
"},{"location":"fep/c390/#history","title":"History","text":"Identity proof is a JSON document that represents a verifiable bi-directional link between a Decentralized Identifier and an ActivityPub actor.
It MUST contain the following properties:
type (REQUIRED): the value of type property MUST be the string VerifiableIdentityStatement.subject (REQUIRED): the decentralized identifier (DID) that represents a cryptographic key belonging to an actor.alsoKnownAs (REQUIRED): the value of this property MUST match the actor ID.proof (REQUIRED): the data integrity proof, as defined by Data Integrity specification.The document MAY contain additional properties.
Identity proofs SHOULD be attached to an actor object, under the attachment property.
The identity proof document MUST contain a data integrity proof, which includes a cryptographic proof and parameters required to verify it. It MUST be created according to the Data Integrity specification, section 4.2 Add Proof. The value of verificationMethod property of the data integrity proof MUST match the value of subject property of the identity proof document.
The resulting data integrity proof MUST be added to identity proof document under the proof key.
Example of an actor object linked to a did:key identifier:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/did/v1\",\n \"https://w3id.org/security/data-integrity/v1\",\n \"https://w3id.org/fep/c390\"\n ],\n \"type\": \"Person\",\n \"id\": \"https://server.example/users/alice\",\n \"inbox\": \"https://server.example/users/alice/inbox\",\n \"outbox\": \"https://server.example/users/alice/outbox\",\n \"attachment\": [\n {\n \"type\": \"VerifiableIdentityStatement\",\n \"subject\": \"did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\",\n \"alsoKnownAs\": \"https://server.example/users/alice\",\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"created\": \"2023-02-24T23:36:38Z\",\n \"verificationMethod\": \"did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\"\n }\n }\n ]\n}\n WARNING: The example above uses eddsa-jcs-2022 cryptosuite, which doesn't have stable specification.
"},{"location":"fep/c390/#proof-verification","title":"Proof verification","text":"The consuming implementations MUST check the authenticity of identity proof document by verifying its data integrity proof. If the proof can not be verified, or if the value of verificationMethod property of the data integrity proof doesn't match the value of subject property of the identity proof, or if the value of alsoKnownAs property of the identity proof doesn't match the actor ID, the identity proof MUST be discarded.
Verification process MUST follow the Data Integrity specification, section 4.4 Verify Proof.
"},{"location":"fep/c390/#linking-identities","title":"Linking identities","text":"The consuming implementations SHOULD treat identities denoted by subject and alsoKnownAs properties of identity proof as belonging to the same entity.
If two actors have identity proofs with the same subject property, they SHOULD be treated as different identities of the same entity.
Servers MUST present identity proofs to clients in their original form. Clients MAY perform independent verification of identity proofs if needed.
"},{"location":"fep/c390/#test-vectors","title":"Test vectors","text":"See fep-c390.feature
"},{"location":"fep/c390/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c4ad/","title":"Index","text":"hide: - navigation description: \"This document proposes a standard for managing viewership history across the Fediverse. It addresses the common issue of posts being repeatedly shown to users on different clients. The goal is to enable servers to track which posts have been viewed by individual users and ensure that clients do not display these posts again. This proposal aims to enhance user experience by preventing the redundant display of already seen posts, commonly requested as \"Hide already seen posts\" or \"stop repeating already seen posts\".\"
"},{"location":"fep/c4ad/#fep-c4ad-viewership-history","title":"FEP-c4ad: Viewership History","text":"Authors Status Date received Tracking issue Discussions Repository AvidSeeker avidseeker7@protonmail.comDRAFT 2024-07-15 #367 Discussions codeberg"},{"location":"fep/c4ad/#summary","title":"Summary","text":"This document proposes a standard for managing viewership history across the Fediverse. It addresses the common issue of posts being repeatedly shown to users on different clients. The goal is to enable servers to track which posts have been viewed by individual users and ensure that clients do not display these posts again. This proposal aims to enhance user experience by preventing the redundant display of already seen posts, commonly requested as \"Hide already seen posts\" or \"stop repeating already seen posts\".
"},{"location":"fep/c4ad/#creating-a-viewership-record","title":"Creating a Viewership Record","text":"When a user views a post, the server should create a record of this event. This record will include the following properties:
id (REQUIRED): the unique identifier of the viewership record.type (REQUIRED): the type of the object MUST be ViewershipRecord.actor (REQUIRED): the actor who viewed the post.object (REQUIRED): the unique identifier of the post that was viewed.published (RECOMMENDED): the date and time at which the post was viewed.Clients should query the server for viewership records to determine whether a post has been viewed by the user. The server should provide an endpoint for clients to fetch viewership records for a specific user.
"},{"location":"fep/c4ad/#handling-viewed-posts","title":"Handling Viewed Posts","text":"When fetching posts, clients must filter out posts that have been viewed by the user based on the viewership records. This ensures that users are not shown the same posts repeatedly.
"},{"location":"fep/c4ad/#copyright","title":"Copyright","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c551/","title":"FEP-c551: Use ECMAScript Modules to Create Conformance Tests for Fediverse Enhancement Proposals","text":"Authors Status Date received Tracking issue Discussions Repository bengo ben@bengo.coDRAFT 2024-07-11 #366 - codeberg"},{"location":"fep/c551/#summary","title":"Summary","text":"This is a proposal to enhance the fediverse by creating test cases for FEPs as ECMAScript Modules.
"},{"location":"fep/c551/#contents","title":"Contents","text":"FEP-d9ad proposes to Create Conformance Tests for Fediverse Enhancement Proposals, and specifies components that all Conformance Tests may use and describe in their Test Specifications. It does not specify a format for implementing FEP-d9ad Conformance Tests in any programming language.
This FEP-c551 proposes to supplement human-readable FEP-d9ad Conformance Tests with implementations of the test as Test Objects exported from Test Modules. Each Test Object has a run function parameterized by a [Test Input] and returning a Promise of a Test Result.
When a tester comes up with a new test for a FEP, they create a human-readable Test Specification describing how to test whether some subject conforms to the FEP.
ECMAScript developers implement Test Specifications as automatable code by using ECMAScript to create Test Functions that execute the test logic and Test Objects that group the Test Function with more info like the test's name, required input, and possible outcomes. Test Objects are distributed in ECMAScript Modules published on the web, e.g. in .js or .mjs files.
Testers invoke the Test Function once for each Test Input, await any returned Promises, and receive a Test Result describing the outcome of running the test.
Test Specifications are human-readable documents that specify the behavior of a test.
Test Specifications SHOULD include Conformance Test Component specifications from FEP-d9ad.
An example of a test specification is fep-521a-test-case.md.
"},{"location":"fep/c551/#test-modules","title":"Test Modules","text":"FEP testers MAY publish implementations of their test specifications as an ECMAScript Module following the recommendations in this proposal. Such modules may be referred to as Test Modules.
Test Modules SHOULD have no imports. This is to ensure portability of the test modules.
Test Modules MAY export a default export object that is a Test Object
Test Modules SHOULD be resilient to being parsed and evaluated in various ECMAScript runtimes (e.g. node.js or a web browser like Firefox).
An example of a test module can be found in activitypub-testing-fep-521a.
"},{"location":"fep/c551/#example-test-module","title":"Example Test Module","text":"export default {\n name: 'invalid script module name',\n run: (input) => ({ outcome: 'passed' }),\n type: ['https://w3id.org/fep/c551#Test'],\n '@context': [\"https://www.w3.org/ns/activitystreams\"],\n}\n"},{"location":"fep/c551/#test-objects","title":"Test Objects","text":"Test Objects are ECMAScript Objects that represent a named, runnable test, e.g. a test specified by a FEP-d9ad Conformance Test.
Test Objects MUST have a property named type whose value is either the string https://w3id.org/fep/c551#Test or an Array containing that string.
Test Objects MUST have a property named name whose value is a string.
Test Objects SHOULD have a property named @context whose value is an Array containing https://www.w3.org/ns/activitystreams.
An example of a test object is exported in activitypub-testing-fep-521a.
Test Objects SHOULD have a property named run whose value is a Test Function].
Test Object run calls SHOULD return a Promise that resolves to a Test Result.
Test Object run functions SHOULD be resilient to being run in various ECMAScript runtimes (e.g. node.js or a web browser like Firefox).
A Test Input is the first parameter to a test's run function.
Test Input MUST be an object. A test with several logically distinct inputs should give each input a name, and add each named input as a property within a top-level input object.
Test Input values SHOULD conform to the specification of the called test's Input spec.
"},{"location":"fep/c551/#test-results","title":"Test Results","text":"Test Results MUST have a property named outcome whose value is a string.
Test Results SHOULD have a property named info whose value is a string.
Test Results MAY have a property named pointer that contextualizes the outcome, e.g. an object with a property for each value that led to the outcome. For example, if a test outcome is failed because some number was too low, you can set the result info to \"number too low\" and pointer to { number: 100 }.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c551/tests/","title":"fep-c551-tests","text":"A test suite that tests for conformance to FEP-c551: Use ECMAScript Modules to Create Conformance Tests for Fediverse Enhancement Proposals.
"},{"location":"fep/c551/tests/#tests","title":"Tests","text":""},{"location":"fep/c551/tests/#fep-c551-module-must-export-test-object","title":"fep-c551 module must export test object","text":"fep-c551-module-must-export-test-objectactivitypub-testing run test \\\n--url=\"$(data-url ./fep-c551-module-must-export-test-object.js)\" \\\n--input.module=\"$(cat <<EOF\nexport default {\n name: 'sample test',\n run: () => {\n return { outcome: 'inapplicable' }\n }\n}\nEOF\n)\"\n The data-url command is provided by the following shell function:
data-url() {\n if [ -z \"$1\" ]; then\n echo \"usage: data-url file\" >&2\n exit 1\n fi\n mimetype=$(file -bN --mime-type \"$1\")\n content=$(base64 < \"$1\")\n echo \"data:$mimetype;base64,$content\"\n}\n"},{"location":"fep/c5a1/","title":"FEP-c5a1: To-do's","text":"Authors Status Date received Tracking issue Discussions Repository Lynn Foster lynn@mikorizal.org DRAFT 2024-01-31 #256 - codeberg"},{"location":"fep/c5a1/#summary","title":"Summary","text":"This document describes an implementation of simple to-do's or tasks based on ActivityPub protocol and Valueflows vocabulary. A to-do is a simple work commitment, and can be created for oneself or another person. Optionally, when the to-do is done, that can be recorded also.
"},{"location":"fep/c5a1/#history","title":"History","text":"This is a parallel submission to FEP-3264 Federated Work Coordination, which supports much more complex project or production planning. Both planning submissions continue the idea of Valueflows extensions to ActivityPub/ActivityStreams based on use case, started by silverpill with FEP-0837 Federated Marketplace.
"},{"location":"fep/c5a1/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/c5a1/#creating-a-to-do","title":"Creating a to-do","text":"A to-do is implemented as a Valueflows Commitment.
Consuming implementations which don't have planning features MAY display plan-related objects similarly to Note objects.
In all cases, the objects can also be discussed using Note objects and replyTo.
Valueflows defines a commitment is defined as A planned economic flow that has been promised by an agent to another agent.
id (REQUIRED): the commitment's unique global identifier.type (REQUIRED): the type of the object MUST be Commitment.attributedTo (REQUIRED): the actor who published the commitment.published (RECOMMENDED): the date and time at which the commitment was published.resourceConformsTo (OPTIONAL): the skill or type of work involved. Could be any URI.effortQuantity (OPTIONAL): the estimated or expected amount and unit of the work. This is an object with two properties:hasUnit (REQUIRED): name of the unit, according to Ontology of units of Measure classification.hasNumericalValue (REQUIRED): amount of the resource.provider (RECOMMENDED): the actor who commits to providing the resource, including doing the work. If not included, it is assumed to be the to actor.receiver (RECOMMENDED): the actor who will be receiving the resource. If not included, it is assumed to be the attributedTo actor.content (RECOMMENDED): the description of what is to be done. The type of content SHOULD be text/html.to (REQUIRED): the audience of the commitment.Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Commitment\": \"vf:Commitment\",\n \"receiver\": \"vf:receiver\",\n \"provider\": \"vf:provider\",\n \"resourceConformsTo\": \"vf:resourceConformsTo\",\n \"effortQuantity\": \"vf:effortQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\"\n }\n ],\n \"type\": \"Create\",\n \"id\": \"https://project.example/activities/ad2f7ee1-6567-413e-a10b-72650cbdc788/create\",\n \"actor\": \"https://project.example/actors/alice\",\n \"object\": {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/todos/ddde9d6f-6f3b-4770-a966-4dkjh8w32e59\",\n \"attributedTo\": \"https://project.example/actors/alice\",\n \"content\": \"Please proofread the document at https://project.example/docs/45, and let me know what you think could be improved.\",\n \"published\": \"2024-05-18T19:22:03.918737Z\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q834191\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"2\"\n },\n \"provider\": \"https://project.example/actors/bob\",\n \"receiver\": \"https://project.example/actors/alice\"\n },\n \"to\": \"https://project.example/actors/bob\"\n}\n"},{"location":"fep/c5a1/#accepting-a-to-do","title":"Accepting a To-do","text":"Accepting or rejecting a to-do is OPTIONAL for this FEP, although it may be required based on the understanding between the actors.
The object of Accept activity MUST be the id of the Commitment object previously sent to the actor.
Activity MAY contain content property for further coordination.
Example:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Accept\",\n \"id\": \"https://project.example/todos/ddde9d6f-6f3b-4770-a966-9kh93jcmljei\",\n \"actor\": \"https://project.example/actors/bob\",\n \"object\": \"https://project.example/todos/ddde9d6f-6f3b-4770-a966-4dkjh8w32e59\",\n \"content\": \"Should be done within a couple days.\",\n \"to\": \"https://project.example/actors/alice\"\n}\n"},{"location":"fep/c5a1/#rejecting-a-to-do","title":"Rejecting a To-do","text":"Accepting or rejecting a to-do is OPTIONAL for this FEP, although it may be required based on the understanding between the actors.
The object of Reject activity MUST be the id of the Commitment object previously sent to the actor.
Activity MAY contain content property indicating the reason for rejection.
Example:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Reject\",\n \"id\": \"https://project.example/todos/8c05f97f-1531-4b70-9ca8-4ee4a09f36a3\",\n \"actor\": \"https://project.example/actors/bob\",\n \"object\": \"https://project.example/todos/ddde9d6f-6f3b-4770-a966-4dkjh8w32e59\",\n \"content\": \"Sorry, my schedule is full for a month.\",\n \"to\": \"https://project.example/actors/alice\"\n}\n"},{"location":"fep/c5a1/#recording-what-actually-was-done","title":"Recording what actually was done","text":"Recording what was done is OPTIONAL for the FEP, although it may be required by the actors involved.
Actual economic activity is represented with an EconomicEvent in Valueflows. In the case of to-do's, it would only occur in response to an earlier to-do commitment. More than one EconomicEvent can be recorded for one Commitment to-do.
The representation of an economic event is a JSON document with the following properties:
id (REQUIRED): the economic event's unique global identifier.type (REQUIRED): the type of the object SHOULD be EconomicEvent. If interoperability with other ActivityPub services is desirable, implementers MAY also use object types from Activity Vocabulary, such as Note.attributedTo (REQUIRED): the actor who published the economic event.content (OPTIONAL): the description of the economic event or communication about the economic event. The type of content SHOULD be text/html.published (RECOMMENDED): the date and time at which the economic event was published.to (REQUIRED): the audience of the economic event.fulfills (REQUIRED): the commitment the economic event is completely or partially fulfilling.resourceConformsTo (OPTIONAL): the type of an economic resource (for to-do's, can be a skill or type of work). Could be any URI. It does not have to match the commitment, but if not included, can be assumed it is the same.effortQuantity (OPTIONAL): the amount and unit of the work done (for to-do's, usually a time quantity). This is an object with two properties:hasUnit (REQUIRED): name of the unit, according to Ontology of units of Measure classification.hasNumericalValue (REQUIRED): amount of the resource.provider (RECOMMENDED): the actor who commits to doing the work. If not included, it is assumed to be the attributedTo actor.receiver (RECOMMENDED): the actor who will be receiving the benefit. If not included, it is assumed to be the to actor.finished (OPTIONAL): set to true if this economic event completes the commitment, or the commitment is no longer open for some reason.{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"EconomicEvent\": \"vf:EconomicEvent\",\n \"fulfills\": \"vf:fulfills\",\n \"receiver\": \"vf:receiver\",\n \"provider\": \"vf:provider\",\n \"resourceConformsTo\": \"vf:resourceConformsTo\",\n \"effortQuantity\": \"vf:effortQuantity\",\n \"hasUnit\": \"om2:hasUnit\",\n \"hasNumericalValue\": \"om2:hasNumericalValue\",\n \"finished\": \"vf:finished\"\n }\n ],\n \"type\": \"Create\",\n \"id\": \"https://project.example/activities/ad2f7ee1-6567-413e-a10b-72650cbdc932/create\",\n \"actor\": \"https://project.example/actors/bob\",\n \"object\": {\n \"type\": \"EconomicEvent\",\n \"id\": \"https://project.example/inputs/ad2f7ee1-6567-413e-a10b-72650cbdc932\",\n \"attributedTo\": \"https://project.example/actors/bob\",\n \"published\": \"2024-10-21T14:16:41.843794Z\",\n \"fulfills\": \"https://project.example/todos/ddde9d6f-6f3b-4770-a966-4dkjh8w32e59\",\n \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q3485549\",\n \"effortQuantity\": {\n \"hasUnit\": \"hour\",\n \"hasNumericalValue\": \"1.5\"\n },\n \"provider\": \"https://project.example/actors/bob\",\n \"receiver\": \"https://project.example/actors/alice\",\n \"finished\": true\n },\n \"to\": \"https://project.example/actors/alice\"\n}\n The Commitment can alternatively be marked as finished, without recording an EconomicEvent. This could occur in cases where no work will be done, or work was done but will not be recorded, or some work was recorded earlier without marking the to-do finished. So, finished does not imply that the to-do was done, only that it is not waiting to be done any more.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\",\n \"vf\": \"https://w3id.org/valueflows/ont/vf#\",\n \"Commitment\": \"vf:Commitment\",\n \"finished\": \"vf:finished\"\n }\n ],\n \"type\": \"Update\",\n \"id\": \"https://project.example/activities/ad2f7ee1-6567-413e-a10b-72650cbdc932/update\",\n \"actor\": \"https://project.example/actors/alice\",\n \"object\": {\n \"type\": \"Commitment\",\n \"id\": \"https://project.example/todos/ddde9d6f-6f3b-4770-a966-4dkjh8w32e59\",\n \"attributedTo\": \"https://project.example/actors/alice\",\n \"published\": \"2024-10-24T16:16:41.843794Z\",\n \"finished\": true\n },\n \"to\": \"https://project.example/actors/alice\"\n}\n"},{"location":"fep/c5a1/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c648/","title":"FEP-c648: Blocked Collection","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@prodromou.nameDRAFT 2023-06-14 #123 - codeberg"},{"location":"fep/c648/#summary","title":"Summary","text":"Users need to review and revise the list of actors they have blocked. This FEP defines a new collection property, the Blocked Collection, which contains the actors that a user has blocked. It also defines a collection of Block activities, which can be used to undo blocks. Finally, it defines inverse properties for both collections, to aid in navigating between the collections and the actors that own them.
The Activity Vocabulary defines a Block activity type. The ActivityPub specification defines how to use a Block activity in the API to block another actor.
However, ActivityPub does not define an efficient way to retrieve the list of actors that a user has blocked. The only way to get this information is to scan the outbox collection for Block activities that were not the object of a later Undo activity. With a large outbox, this can be inefficient and slow.
The followers and following properties of an actor are collections of objects in the actor's social graph. By analogy, the new blocked property defined in this document is a collection of other actors that the actor has blocked.
One use case for the list of blocked actors is to allow the user to review and potentially undo the blocks. However, the ActivityPub specification requires the id of the Block activity to undo the block.
To make it easier to undo blocks, the blocks property of an actor is a collection of Block activities, which include the id of the activity, the object that was blocked. These activities can be used to undo the block.
Of the two properties, blocks is most useful for client applications, since the collection of blocked actors can be derived from the object property of each Block activity. Some implementers, however, may prefer the blocked property for parallelism with the followers and following properties. This specification defines both properties.
The context document for the blocked property is as follows:
{\n \"@context\": {\n \"bl\": \"https://purl.archive.org/socialweb/blocked#\",\n \"blocked\": {\n \"@id\": \"bl:blocked\",\n \"@type\": \"@id\"\n },\n \"blocks\": {\n \"@id\": \"bl:blocks\",\n \"@type\": \"@id\"\n },\n \"blockedOf\": {\n \"@id\": \"https://www.w3.org/ns/activitystreams#blockedOf\",\n \"@type\": \"@id\"\n },\n \"blocksOf\": {\n \"@id\": \"https://www.w3.org/ns/activitystreams#blocksOf\",\n \"@type\": \"@id\"\n },\n }\n}\n The context document is available at the URL https://purl.archive.org/socialweb/blocked.
The main context URL will always have the latest version of the context document for this vocabulary. Additional context URLs are available with version numbers, to allow implementers to define their own level of tolerance to changes in the vocabulary. The version stamps use semantic versioning. The version-stamped context URLs are:
Context URL Purposehttps://purl.archive.org/socialweb/blocked/2.0.0 The exact version of the context used in this document. This alias is useful for implementers that need an immutable document. https://purl.archive.org/socialweb/blocked/2.0 The exact set of terms defined in this document, possibly with bug fixes or documentation changes. This alias is useful to get the latest version of the context document, without introducing any new terms that might conflict with other vocabularies. https://purl.archive.org/socialweb/blocked/2 The set of terms defined in this document, plus possibly new terms, as well as bug fixes and documentation changes. This alias allows implementers to get backwards-compatible changes to the vocabulary, without having to change their code."},{"location":"fep/c648/#properties","title":"Properties","text":""},{"location":"fep/c648/#blocked","title":"blocked","text":"URI https://purl.archive.org/socialweb/blocked#blocked Notes The blocked property of an actor is an ordered collection of ActivityPub actors. The blocked collection SHOULD include all actors blocked by the actor, except for those that have been unblocked by an Undo activity. Each actor in the blocked collection MUST be unique. The blocked collection MUST be sorted in reverse chronological order, with the most recently added actor first. (Users are most likely to want to see who they have blocked recently, so these actors should be ordered first.) As with other ActivityPub properties, the blocked property MAY be referenced in the actor by id or as an embedded node object. Domain Object (an ActivityPub actor) Range OrderedCollection Functional true"},{"location":"fep/c648/#blocks","title":"blocks","text":"URI https://purl.archive.org/socialweb/blocked#blocks Notes The blocks property of an actor is an ordered collection of Block activities. The blocks collection SHOULD include all Block activities by the actor, except for those that have been reverted by an Undo activity. Each activity in the blocks collection MUST be unique. The blocks collection MUST be sorted in reverse chronological order, with the most recent activity first. (Users are most likely to want to see who they have blocked recently, and possibly unblock them, so these activities should be ordered first.) As with other ActivityPub properties, the blocks property MAY be referenced in the actor object by id or as an embedded node object. Domain Object (an ActivityPub actor) Range OrderedCollection Functional true"},{"location":"fep/c648/#blockedof","title":"blockedOf","text":"URI https://purl.archive.org/socialweb/blocked#blockedOf Notes The value of the blockedOf property of a collection is the actor for whom the collection is the value of its blocked property. It is the inverse property of blocked. As with other ActivityPub properties, the blockedOf property MAY be referenced in the actor by id or as an embedded node object. Domain OrderedCollection Range Object (an ActivityPub actor) Functional true"},{"location":"fep/c648/#blocksof","title":"blocksOf","text":"URI https://purl.archive.org/socialweb/blocked#blocksOf Notes The value of the blocksOf property of a collection is the actor for whom the collection is the value of its blocks property. It is the inverse property of blocks. As with other ActivityPub properties, the blocksOf property MAY be referenced in the actor by id or as an embedded node object. Domain OrderedCollection Range Object (an ActivityPub actor) Functional true"},{"location":"fep/c648/#examples","title":"Examples","text":""},{"location":"fep/c648/#blocked-property","title":"blocked property","text":"A publisher can include the blocked collection in the properties of an actor.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/blocked\"\n ],\n \"id\": \"https://example.com/evanp\",\n \"type\": \"Person\",\n \"name\": \"Evan Prodromou\",\n \"inbox\": \"https://example.com/evanp/inbox\",\n \"outbox\": \"https://example.com/evanp/outbox\",\n \"following\": \"https://example.com/evanp/following\",\n \"followers\": \"https://example.com/evanp/followers\",\n \"liked\": \"https://example.com/evanp/liked\",\n \"blocked\": \"https://example.com/evanp/blocked\",\n \"to\": \"as:Public\"\n}\n"},{"location":"fep/c648/#a-blocked-collection","title":"A blocked collection","text":"Retrieving the blocked collection would provide an ordered collection of ActivityPub actors that have been blocked.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/blocked\",\n {\"custom\": \"https://example.com/ns/custom\"}\n ],\n \"id\": \"https://example.com/evanp/blocked\",\n \"type\": \"OrderedCollection\",\n \"attributedTo\": \"https://example.com/evanp\",\n \"blockedOf\": \"https://example.com/evanp\",\n \"name\": \"Evan Prodromou's Blocked Collection\",\n \"orderedItems\": [\n {\n \"type\": \"Person\",\n \"id\": \"https://spam.example/spammer\",\n \"name\": \"Irritating Spammer\"\n },\n {\n \"type\": \"Application\",\n \"id\": \"https://alarmclock.example/alarmclock\",\n \"name\": \"Badly-Behaved Alarm Clock App\"\n }\n ]\n}\n"},{"location":"fep/c648/#blocks-property","title":"blocks property","text":"Similarly, a publisher can include the blocks collection in the properties of an actor. Note that here, the blocks collection is referenced as an embedded node object, rather than a URL. It includes useful metadata about the collection.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/blocked\"\n ],\n \"id\": \"https://example.com/evanp\",\n \"type\": \"Person\",\n \"name\": \"Evan Prodromou\",\n \"inbox\": \"https://example.com/evanp/inbox\",\n \"outbox\": \"https://example.com/evanp/outbox\",\n \"following\": \"https://example.com/evanp/following\",\n \"followers\": \"https://example.com/evanp/followers\",\n \"liked\": \"https://example.com/evanp/liked\",\n \"blocks\": {\n \"id\": \"https://example.com/evanp/blocks\",\n \"type\": \"OrderedCollection\",\n \"totalItems\": 2,\n },\n \"to\": \"as:Public\"\n}\n"},{"location":"fep/c648/#a-blocks-collection","title":"A blocks collection","text":"Retrieving the blocks property value would provide an ordered collection of Block activities that the user has performed and not undone.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/blocked\",\n {\"custom\": \"https://example.com/ns/custom\"}\n ],\n \"id\": \"https://example.com/evanp/blocks\",\n \"type\": \"OrderedCollection\",\n \"attributedTo\": \"https://example.com/evanp\",\n \"blocksOf\": \"https://example.com/evanp\",\n \"name\": \"Evan Prodromou's Blocks Collection\",\n \"orderedItems\": [\n {\n \"type\": \"Block\",\n \"id\": \"https://example.com/evanp/block/2\",\n \"object\": {\n \"type\": \"Person\",\n \"id\": \"https://spam.example/spammer\",\n \"name\": \"Irritating Spammer\"\n },\n \"published\": \"2023-04-15T00:00:00Z\"\n },\n {\n \"type\": [\"custom:Disallow\", \"Block\"],\n \"id\": \"https://example.com/evanp/block/2\",\n \"object\": {\n \"type\": \"Application\",\n \"id\": \"https://alarmclock.example/alarmclock\",\n \"name\": \"Badly-Behaved Alarm Clock App\"\n },\n \"published\": \"2022-12-25T00:00:00Z\"\n }\n ]\n}\n"},{"location":"fep/c648/#processing-requirements","title":"Processing requirements","text":"When a server receives a Block activity from a client through the ActivityPub API, it SHOULD add the activity to the blocks collection of the actor that performed the block. The server MAY also add the blocked actor to the blocked collection of the blocking actor.
When a server receives an Undo activity for a Block activity through the ActivityPub API, it SHOULD remove the Block activity from the blocks collection of the actor that undid the block. The server MAY also remove the blocked actor from the blocked collection of the blocking actor.
The blocked and blocks collections are very sensitive. Actors on the blocked list may be harassing or abusive. If they find themselves on a user's blocklist, they may retaliate against the user. Consequently, the blocked and blocks collections SHOULD NOT be publicly readable.
By default, implementations SHOULD NOT allow read access to the blocked or blocks collections to any actor other than the user that owns the collection.
Some users may want to share their blocklist with other actors. Shared blocklists are an important tool for user safety on monolithic social networks and on the social web. Implementations MAY allow a user to share their blocked collection with other actors. Implementations SHOULD inform the user of the risks of sharing their blocklist with the wrong actors.
Sharing the blocks collection is less useful, since Block activities are usually not readable by anyone but the blocking actor.
The onepage.pub server implements the blocked collection.
activitypub-express implements a blocked property in the streams collection of an actor, including the blocked objects only. The developers' experience was that storing objects only made it hard to Undo a block, since the full Activity object's id is needed. Metadata about the block activity, such as the date, is also lost.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c7d3/","title":"FEP-c7d3: Ownership","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository silverpill @silverpill@mitra.socialWITHDRAWN 2024-06-04 2025-02-21 #323 Discussions codeberg"},{"location":"fep/c7d3/#summary","title":"Summary","text":"In this document we discuss the concept of ownership, as applied to ActivityPub objects.
Warning
This FEP has been superseded by FEP-fe34.
"},{"location":"fep/c7d3/#rationale","title":"Rationale","text":"ActivityPub standard does not specify authentication and authorization mechanisms.
Many implementations use actor and attributedTo properties (defined in Activity Vocabulary) to determine the validity of activities and objects. This proposal attempts to formalize the current practices and provide guidance for implementers.
The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/c7d3/#ownership","title":"Ownership","text":"Ownership is indicated by a property of an ActivityPub object. The name of this property differs depending on the object type:
id property.actor property, which describes the actor that performed the activity. This actor is considered to be the owner of the activity.attributedTo property, which describes the actor to which the object is attributed. This actor is considered to be the owner of the object.owner and controller properties.The owner of an object MUST be an actor.
Warning
According to Activity Vocabulary, actor and attributedTo properties can contain references to multiple actors. These scenarios are not covered by this document and implementers are expected to determine the appropriate authentication and authorization procedures on a case-by-case basis.
Note
In subsequent sections, \"objects\" and \"activities\" will be referred to as simply \"objects\".
"},{"location":"fep/c7d3/#origin","title":"Origin","text":"Object identifiers are grouped together into protection domains called \"origins\". This concept is similar to the \"web origin\" concept described in RFC-6454, and origins of object IDs are computed by the same algorithm.
The same-origin policy determines when a relationship between objects can be trusted.
Note
There might be other ways to establish trust, but they are not covered by this document.
"},{"location":"fep/c7d3/#identifiers-and-ownership","title":"Identifiers and ownership","text":"Identifier of an object and identifier of its owner MUST have the same origin.
"},{"location":"fep/c7d3/#authentication","title":"Authentication","text":"The object is considered authentic if any of the following conditions are met:
POST request contained a valid HTTP signature created using a key whose owner has the same origin as the object owner.If none of these conditions are met, the object MUST be discarded.
If signature verification is performed, the key owner SHOULD match the object owner.
Note
In some cases, consumers can process unauthenticated objects if the risk is deemed acceptable.
"},{"location":"fep/c7d3/#delivered-to-inbox","title":"Delivered to inbox","text":"If the object was delivered to inbox and its authentication fails, the recipient SHOULD fetch it and repeat the authentication procedure.
"},{"location":"fep/c7d3/#emdedded-objects","title":"Emdedded objects","text":"If the embedded and the containing objects have owners with different origins, the authenticity of the embedded object MUST be verified independently either by fetching it from the server of origin, or by verifying its FEP-8b32 integrity proof.
"},{"location":"fep/c7d3/#anonymous-objects","title":"Anonymous objects","text":"An object without an ID can only exist when embedded within another object. It has the same owner as the parent object and it is considered authentic when the parent object is authentic.
"},{"location":"fep/c7d3/#unattributed-objects","title":"Unattributed objects","text":"An object without an explicit owner is owned by the server. Such object MUST be considered authentic only if fetched from the location that has the same origin as its ID.
"},{"location":"fep/c7d3/#authorization","title":"Authorization","text":"The actor that creates the object MUST be its owner.
If activity modifies or deletes an object, its owner SHOULD match the object's owner. If owners are different, their IDs MUST have the same origin.
Examples:
Create, Update and Delete activities, and objects indicated by their object property SHOULD have the same owner.Undo activity and object indicated by its object property SHOULD have the same owner.Add and Remove activities, and objects indicated by their target property SHOULD have the same owner.Announce and Like activities don't modify objects indicated by their object property, therefore their owners can be different.When ownership changes, the new owner ID MUST have the same origin as the old owner ID.
"},{"location":"fep/c7d3/#access-control","title":"Access control","text":"When a protected object is fetched, the GET request MUST contain a HTTP signature created using a key whose owner SHOULD belong to object's intended audience. If key owner doesn't belong to intended audience, its ID MUST have the same origin as one of the actors in object's intended audience.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/c893/","title":"FEP-c893: DOAP","text":"Authors Status Date received Tracking issue Discussions Repository AvidSeeker avidseeker7@protonmail.comDRAFT 2024-07-15 #372 Discussions codeberg"},{"location":"fep/c893/#summary","title":"Summary","text":"This proposal introduces a standardized method for describing Fediverse projects using the Description of a Project (DOAP) format. The proposal outlines the creation of doap.jsonld file that includes details about implemented federation protocols and supported Fediverse Enhancement Proposals (FEPs). This makes it easier for developers and users to understand the capabilities and compatibility of various Fediverse projects.
doap.jsonld File","text":"Here is an example structure for the doap.jsonld file:
{\n \"@context\": {\n \"doap\": \"http://usefulinc.com/ns/doap#\",\n \"foaf\": \"http://xmlns.com/foaf/0.1/\",\n \"rdfs\": \"http://www.w3.org/2000/01/rdf-schema#\",\n \"doap:description\": {\n \"@id\": \"doap:description\",\n \"@container\": \"@language\"\n },\n \"doap:shortdesc\": {\n \"@id\": \"doap:shortdesc\",\n \"@container\": \"@language\"\n }\n },\n \"@type\": \"doap:Project\",\n \"doap:name\": \"ExampleProject\",\n \"doap:homepage\": \"https://example.org\",\n \"doap:description\": {\n \"en\": \"ExampleProject is a Fediverse client/server that supports multiple protocols and enhancements.\",\n \"es\": \"ExampleProject es un cliente/servidor de Fediverse que admite m\u00faltiples protocolos y mejoras.\"\n },\n \"doap:shortdesc\": {\n \"en\": \"Tools and vocabulary for describing community-based software projects.\",\n \"es\": \"Vocabulario y herramientas para describir proyectos de software comunitarios.\"\n },\n \"doap:created\": \"2022-01-01\",\n \"doap:logo\": \"https://example.org/logo.png\",\n \"doap:screenshots\": [\n \"https://example.org/screenshot1.png\",\n \"https://example.org/screenshot2.png\"\n ],\n \"doap:category\": [\n \"http://software.freshmeat.net/browse/1020/\",\n \"http://osdir.com/Downloads+index-req-viewsdownload-sid-201.phtml\"\n ],\n \"doap:repository\": {\n \"@type\": \"doap:GitRepository\",\n \"doap:browse\": \"https://github.com/example/exampleproject/\",\n \"doap:location\": \"https://github.com/example/exampleproject.git\"\n },\n \"doap:release\": {\n \"@type\": \"doap:Version\",\n \"doap:created\": \"2024-07-15\",\n \"doap:name\": \"v1.0.0\",\n \"doap:revision\": \"1.0.0\"\n },\n \"doap:maintainer\": [\n {\n \"@type\": \"foaf:Person\",\n \"foaf:name\": \"John Doe\",\n \"foaf:homepage\": \"https://github.com/johndoe\"\n },\n {\n \"@type\": \"foaf:Person\",\n \"foaf:name\": \"Jane Smith\",\n \"foaf:homepage\": \"https://github.com/janesmith\"\n }\n ],\n \"doap:implements\": [\n \"https://activitypub.rocks/specification\",\n \"https://diasporafoundation.org\"\n ],\n \"doap:supportedFEPs\": [\n \"https://fediverse.org/fep-0001\",\n \"https://fediverse.org/fep-0002\"\n ]\n}\n"},{"location":"fep/c893/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/cb76/","title":"FEP-cb76: Content Addressed Vocabulary","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository a a@trwnh.comWITHDRAWN 2022-11-29 2025-04-07 #41 Discussions codeberg"},{"location":"fep/cb76/#summary","title":"Summary","text":"JSON-LD context definitions typically live at some URI which gets used as a namespace. It is generally expected that the URI is long-lived, and often the context document is retrievable from that URI, but sometimes these links break due to technical errors, expired domains, and other such issues. This FEP proposes adopting a solution proposed by [CAV] for any extension terms defined within other FEPs, as well as optionally for standard vocabulary.
"},{"location":"fep/cb76/#1-defining-fep-extension-terms","title":"1. Defining FEP extension terms","text":"Each extension term MUST have a specification text.
This specification is then used to calculate a SHA256 hash, which can be used as a URN within @context in lieu of a namespaced property.
Say we want to define a new ActivityStreams Vocabulary [AS2V] term, which will be a sub-type of Activity called Message, with the following specification text:
Indicates that the actor is sending a direct message.\n We obtain the SHA256 hash of this specification text:
$ echo -n \"Indicates that the actor is sending a direct message.\" | sha256sum\n\nbab53e61faa0ddecce6991df4c26259b6c2e1b880cef12225033590fcaad1aaa -\n We can now use this hash to extend ActivityPub [AP] with a Message activity:
{\n\"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"Message\": \"urn:sha256:bab53e61faa0ddecce6991df4c26259b6c2e1b880cef12225033590fcaad1aaa\"\n }\n],\n\"actor\": \"https://social.example/~alice\",\n\"type\": \"Message\",\n\"to\": \"https://bob.example.com\",\n\"content\": \"hi friend\"\n}\n"},{"location":"fep/cb76/#2-storing-fep-extension-terms-within-the-fep-git-repository","title":"2. Storing FEP extension terms within the FEP Git repository","text":"(This section is non-normative.)
A directory named context/ should be created to store content addressed vocabulary extensions associated with FEPs. FEPs that propose vocabulary extensions MUST create a text file called fep-abcd_property.txt within this directory, where abcd is the identifier of the associated FEP, property is the recommended shorthand name of the property, and the contents of the text file are the specification text. FEP authors SHOULD try to use unique property names within the existing FEP vocabulary, unless an FEP is intended to supersede or replace an older FEP.
A context.jsonld file SHOULD be generated any time a new vocabulary term is finalized, with its contents being a definition of every finalized vocabulary term.
In Example 1, we defined a Message activity type to be used for ActivityPub [AP] direct messaging. Say we now want to draft an FEP for this definition.
We follow the FEP process as defined in FEP-a4ed to title and identify our FEP proposal. Applying this process to our proposal of A Message activity for direct messaging yields the identifier FEP-0ac6. Combined with our specification text of Indicates that the actor is sending a direct message, this yields the following text file fep-0ac6_Message.txt:
Indicates that the actor is sending a direct message\n The text file is then stored at context/fep-0ac6_Message.txt, and when it is finalized, its SHA256 hash is added to context/context.jsonld:
{\n \"@context\": {\n \"Message\": \"urn:sha256:bab53e61faa0ddecce6991df4c26259b6c2e1b880cef12225033590fcaad1aaa\"\n }\n}\n"},{"location":"fep/cb76/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/cd47/","title":"FEP-cd47: Federation-friendly Addressing and Deduplication Use-Cases","text":"Authors Status Date received Tracking issue Discussions Repository Bumblefudge bumblefudge@learningproof.xyzDRAFT 2024-05-31 #319 Discussions codeberg"},{"location":"fep/cd47/#summary","title":"Summary","text":"A proposed taxonomy of ways to make various kinds of ActivityPub data identifiable across locations to simplify higher-order functions like moderation receipts, tracking for trust and safety purposes, data migration, compliance, etc. This is intended as a light-weight and informational/meta-technical design document, not a specification or an extension.
"},{"location":"fep/cd47/#rationale","title":"Rationale","text":"While Camille Fran\u00e7oise's originial \"ABCs\" paper was focused on disinformation specifically, it lays out the basic taxonomy I will be using here between Actors, Behavior, and Content, as three different vectors for disinformation, but also for abuse, trust and safety issues, inauthentic activity, copyright compliance, hate speech compliance, etc. The goal of this document is to collect the addressable and deduplication requirements for all of these related \"content-specific\" liabilities and use-cases, and inform the design of addressing and deduplication primitives in future FEPs.
"},{"location":"fep/cd47/#terminology","title":"Terminology","text":"If we scope this exercise to data conformant to the ActivityStreams data model as extended and federated by ActivityPub, we could scope these categories as:
Actors: fediverse \"accounts\", bots, etc.Note object, for example), media \"attachments\" (which we could further constrain to defined media-types in the HTML sense, for simplicity), and links out to other data (URLs, but also other URIs TBD?)What we mean by \"addressing\" given the scope defined above is tricky, because there is a URI-based system of ids inherent to the JSON-LD data model that the AS and AP specifications build on, and most references to addressing or identification in the core specifications refer only to this graph-friendly but static scheme of resources and links. In practice to date, this has been coextensive with HTTPS URLs and DNS-resolveable domains. Whether non-HTTPS URLs could or should be used as ids or @ids is out-of-scope of this use-case document, and should be considered a design/implementation decision treated elsewhere. Regardless, today's Fediverse data is overwhelmingly (perhaps exclusively?) addressed by domain-based (and domain-dependent) ids that any new system should be able to handle and deduplicate in its current form, at least as \"legacy\" data if any breaking changes were proposed to these practices or to the core specifications.
Today, most actors and behaviors are addressed by their ids that are also their current HTTPS \"location\"; content is similarly location-addressed most of the time, except insofar as some attachments are sometimes renamed in bucket storage configurations. All of these addresses are brittle vis-a-vis duplication, migration, and \"server death\"-- moving them to a new location creates a new address for identical content/referent, often without a \"forwarding\" redirection (HTTP-code-based or otherwise), or a backlink to its previous address, while a server going down suddenly just breaks all those links with no verifiable remedy.
One form of location-independent addressing is \"content-addressing\", the most common form of which is addressing canonicalizable contents by hashing them in canonicalized form, often used in key/value stores, package managers, etc. It can sometimes be trickier than expected, however, to excise the location (or all properties that break if location changes) from the canonical form hashed to identify a piece of content, or an actor or a specific behavior.
"},{"location":"fep/cd47/#high-level-use-cases","title":"High-level Use-Cases","text":"id patterns, annotations, bucket-storage URL schemes, etc.id!) are perhaps the simplest to content-address. These do not need to be addressed by unknown, external, or future parties, but MAY benefit from addressing the same way other content is addressed, e.g. when string-comparing uploads or pre-published content to a content-addressed blocklist before publishing.@Context files or javascript files, Emoji sets, or other shared resources that might mutate at a static address be brought into scope? Is it redundant or dangerous to have similar-but-different content addressing mechanisms for those kinds of resource verifiability running parallel to Actor, Behavior, and Content verifiability?ids? How to deduplicate across migrations that change id property?Normative
Informational
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/d36d/","title":"Index","text":"hide: - navigation description: \"New instances on the threadiverse (servers that implement ActivityPub with FEP-1b12) are often seeded with forums for common interests, leading to multiple servers having similar forums. Users may dislike having to follow what they perceive to be \"duplicate\" forums or keep up with multiple discussions on the same topic across multiple servers. This document describes a method for allowing Group actors to share content to reduce posting of a single link multiple times, which reduces what users see as \"duplicate\" posts and fragmented conversations across multiple forums.\"
"},{"location":"fep/d36d/#fep-d36d-sharing-content-across-federated-forums","title":"FEP-d36d: Sharing Content Across Federated Forums","text":"Authors Status Date received Tracking issue Discussions Repository Zack Dunn zack@tilde.teamDRAFT 2023-07-01 #125 - codeberg"},{"location":"fep/d36d/#summary","title":"Summary","text":"New instances on the threadiverse (servers that implement ActivityPub with FEP-1b12) are often seeded with forums for common interests, leading to multiple servers having similar forums. Users may dislike having to follow what they perceive to be \"duplicate\" forums or keep up with multiple discussions on the same topic across multiple servers. This document describes a method for allowing Group actors to share content to reduce posting of a single link multiple times, which reduces what users see as \"duplicate\" posts and fragmented conversations across multiple forums.
FEP-1b12 introduces federated forums and is implemented by Lemmy, /kbin, and Friendica.
The site reddit has a feature for grouping its forums, called subreddits, into a new forum, called a multireddit. A multireddit is a feed made up of the combination of each of its constituent subreddits and allows a user an easy way to keep up with multiple related forums. Because subreddits can be in multiple multireddits, multireddits don't affect moderation of links posted to individual subreddits and a link can be posted to more than one constituent subreddit within a multireddit.
"},{"location":"fep/d36d/#group-to-group-follows","title":"Group to Group Follows","text":"When a moderator of a federated forum determines that their forum overlaps in topic with another forum, they can direct the Group actor to send a Follow activity to the other forum's Group actor. FEP-1b12 specifices that a group should automatically respond with an Accept/Follow, but this document overrides that for Follow activities with an actor of type Group. After receiving a Follow activity from another Group, the group MAY automatically respond with an Accept/Follow or a moderator may instruct the group to reply with a Reject/Follow. After replying with a Accept/Follow activity, the group that received the Follow activity MAY automatically add the first group to its following collection, creating a symmetric relationship.
This document makes no change to the handling of an Undo/Follow activity. If a group receives an Undo/Follow from a Group actor, it MAY automatically remove the other group from its following collection.
When a group receives an activity in its inbox, it SHOULD perform automatic validation as described in FEP-1b12. If that validation includes deduplication (via the url property of the activity's object, the url of any attachements, or any other method), that deduplication validation MUST include objects received from followed groups. If an activity fails this deduplication validation, the group MUST respond with a Reject activity where the object property is the object from the inbox activity and the target object is the object that the new object duplicates. This ensures that content is posted only once across related forums and a forum can provide navigation to an original post when a user tries to post a duplicate.
If the incoming activity is deemed valid, the group MUST handle it according to FEP-1b12 handling of valid activities.
"},{"location":"fep/d36d/#references","title":"References","text":"[FEP-1b12] Felix Ableitner, FEP-1b12: Group federation
"},{"location":"fep/d36d/#copyright","title":"Copyright","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/d556/","title":"FEP-d556: Server-Level Actor Discovery Using WebFinger","text":"Authors Status Type Date received Date final Tracking issue Discussions Repository Steve Bate svc-fep@stevebate.netFINAL implementation 2024-01-20 2025-03-15 #243 - codeberg"},{"location":"fep/d556/#summary","title":"Summary","text":"Server-level ActivityPub actors support server-wide functionality rather than representing a user or the software equivalent (sometimes called a bot). This proposal describes how to discover a server-level actor's URI using WebFinger.
"},{"location":"fep/d556/#terminology","title":"Terminology","text":"The term server is not well-defined. For the purposes of this document, an server is an origin SameOriginPolicy having the same URL prefix (scheme, host, port). The term does not imply anything about network or software architecture. An server could consist of many server processes behind a load-balancing reverse proxy. Or, inversely, a single server process could host many servers (multi-tenant architecture).
Some implementations could have multiple actors to support different server-level roles (moderation, administration, etc.). In this document, the term server-level actor will be used to describe these kind of actors. The term Server Actor or Application Actor is a special, but common, case where there is a single server-level actor.
The term Server is used extensively in the ActivityPub Recommendation, although it is mostly undefined beyond which activities a server may process. The term is closely related to Mastodon's use of the word instance, although this is not the only way the word is used in online discussions.
NOTE: The standard role and responsibilities of server-level actors are not defined here (or elsewhere, at the time of this submission). Several implementations have something they call an Instance Actor or Application Actor, but they may or may not be interoperable since no standard behaviors have been defined at this time.
"},{"location":"fep/d556/#use-cases","title":"Use Cases","text":"Although this FEP does not define specific uses of server-level actors, it's useful to know how they are, or could be, used in practice. The following are a some potential use cases:
Signing Fetch Requests: This appears to be the most common use case. The requirement for this is a combination of: limiting access to actor profiles by requiring HTTP Signatures (i.e., \"authorized fetch\") and tightly-coupling actor profiles with their public keys. This results in profile/key fetch loops. (InstanceActor). To mitigate this undesirable behavior, one technique is to have a third-party actor (often called an \"instance actor\") sign every fetch request. Actor discovery is not required for this use case, but it's mentioned here because it appears to be the motiving use case for FEP-2677, which has some similarities to this one.
Relay Support: A server-level actor can be used for subscribing to a relay (often using an ActivityPub Follow request) and receiving inbox messages.
Server-level Subscriptions: Some implementations, like Pleroma, provide an actor that can be followed to receive all messages from an \"instance\".
Moderation: A server-level actor may be used to federate moderation-related content (actor or domain blocks, post flags, etc.) or provide a publication proxy to shield the identity of moderators performing the actions.
Announcements: An server-level actor could be used to public server news. For example, it could publish content including announcements about new features, maintenance schedules, or updates.
Object Attribution: Some server implementations allow some objects to be attributed to the server rather than an individual user or account.
Administration: A server-level actor could be used to share information about software issue (including reports from users), available updates, and security vulnerabilities and mitigations.
To discover an server-level actor's URI, query WebFinger with the server prefix as the resource query parameter.
Example Request:
GET /.well-known/webfinger?resource=https://server.example/\n Response: {\n \"subject\": \"https://server.example/\",\n \"links\": [\n {\n \"rel\": \"https://www.w3.org/ns/activitystreams#Service\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://server.example/actor\"\n }\n ]\n}\n The subject would typically be the resource URI. This proposal does not depend on any specific URI for subject, although the ActivityPub actor URI is recommended. The Server-level Actor's URI will be the href property of a link with a rel (relation type) property of https://www.w3.org/ns/activitystreams#Service (W3C AS2 Service Primer). The type of the Server-level Actor itself is not required to be the same as the relation type.
The https://www.w3.org/ns/activitystreams#Service rel value may be replaced with self if there is no ambiguity between the server-level actor and user's actor in a single actor server (see discussion of single-actor servers).
A http://webfinger.net/rel/profile-page rel (WebFinger Relations) can be used to link to server metadata (possibly with multiple content types). However, the structure of the target metadata has not been defined at this time. For example, the following links refer to profile data in HTML and JSON-LD formats.
{\n \"subject\": \"https://server.example/\",\n \"links\": [\n {\n \"rel\": \"https://www.w3.org/ns/activitystreams#Service\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://server.example/actor\"\n },\n {\n \"rel\": \"http://webfinger.net/rel/profile-page\",\n \"type\": \"text/html\",\n \"href\": \"https://server.example/profile\"\n },\n {\n \"rel\": \"http://webfinger.net/rel/profile-page\",\n \"type\": \"application/ld+json\",\n \"href\": \"https://server.example/profile\"\n }\n ]\n}\n If multiple server-level actor links are returned, the links can be disambiguated by adding metadata to the links using standard WebFinger properties. For example, an implementation could have different server-level actors that serve different purposes.
It's also possible that another FEP will define standard rel URIs for common roles. In that case, those FEP role URIs SHOULD be preferred.
NOTE: The definition of standard server-level actor roles is outside the scope of this FEP.
{\n \"subject\": \"https://server.example/\",\n \"links\": [\n {\n \"rel\": \"https://www.w3.org/ns/activitystreams#Service\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://server.example/actor\",\n \"properties\": {\n \"http://schema.org/roleName\": \"administration\"\n }\n },\n {\n \"rel\": \"https://www.w3.org/ns/activitystreams#Service\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://server.example/actor\",\n \"properties\": {\n \"http://schema.org/roleName\": \"moderation\"\n }\n }\n ]\n}\n In this example, the same actor used used for administration and moderation. However, the example would also be valid if the actors were different. It's possible that for some use cases a role might be further refined. For example, additional properties might specify a geographical region for a role.
"},{"location":"fep/d556/#single-actor-servers","title":"Single Actor Servers","text":"A developer of a single-actor (user actor) server may want that user to have a URI corresponding to the server prefix although it's not intended to be an server-level actor. This scenario, which is not expected to be a common one, can be supported by returning multiple links in the WebFinger response.
{\n \"subject\": \"https://server.example/\",\n \"links\": [\n {\n \"rel\": \"https://www.w3.org/ns/activitystreams#Service\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://server.example/server-actor\"\n },\n {\n \"rel\": \"self\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://server.example/user-actor\"\n }\n ]\n}\n If an application is only interested in a the Server Actor or User Actor specifically, it can use the rel query parameter to filter the links, as described in the WebFinger specification (if supported by the Webfinger service implementation).
For example, to only query the User Actor URI, the query would be:
GET /.well-known/webfinger?resource=https://server.example/&rel=self\n {\n \"subject\": \"https://server.example/\",\n \"links\": [\n {\n \"rel\": \"self\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://server.example/user-actor\"\n }\n ]\n}\n"},{"location":"fep/d556/#implementations","title":"Implementations","text":"Known implementations include:
GET /.well-known/webfinger?resource=https://mastodon.social/\nHost: https://mastodon.social\n or using Mastodon account-based URI: GET /.well-known/webfinger?resource=acct:mastodon.social@mastodon.social\nHost: https://mastodon.social\n {\n \"subject\": \"acct:mastodon.social@mastodon.social\",\n \"aliases\": [\n \"https://mastodon.social/actor\"\n ],\n \"links\": [\n {\n \"rel\": \"http://webfinger.net/rel/profile-page\",\n \"type\": \"text/html\",\n \"href\": \"https://mastodon.social/about/more?instance_actor=true\"\n },\n {\n \"rel\": \"self\",\n \"type\": \"application/activity+json\",\n \"href\": \"https://mastodon.social/actor\"\n },\n {\n \"rel\": \"http://ostatus.org/schema/1.0/subscribe\",\n \"template\": \"https://mastodon.social/authorize_interaction?uri={uri}\"\n }\n ]\n}\n Some differences between the Mastodon implementation and this proposal include:
It does not support standard WebFinger filtering by rel.
The subject is the Mastodon-specific account URI for the server-level actor rather than the recommended ActivityPub actor URI.
Since no user-related actor link is provided for the server resource, the self rel value can be used without ambiguity
FEP-2677 suggests using NodeInfo for a similar purpose. There are several disadvantages of this compared to using WebFinger.
rel relation to the NodeInfo index document. This may have surprising effects on some consuming implementations. This proposal is using WebFinger in standard ways.rel value, a as:Service (Primer) is the type suggested by the W3C ActivityStreams Primers for this kind of resource rather than as:Application (Primer). (Note this is distinct from the type specified in the server-level actor resource that's linked from WebFinger.)as:Application type. This proposal has no constraints on the actor type. The as:Service URI is only used for the link relation type.Although the definition isn't clear, the \"Application Actor\" in FEP-2677 appears to be a proxy for a software \"application\" (not defined, but appears to be a similar concept to \"server\" in this proposal). For example, there's a discussion about attaching application metadata to the actor. In this proposal, there is no server proxy actor (although that's not prohibited). There is a server WebFinger resource with linked server-level service actors, but the server resource is not necessarily an actor itself.
FEP-2c59 discusses how to discover WebFinger resource URIs from an ActivityPub actor resource. This is not related to server-level actor discovery.
FEP-4adb discusses dereferencing identifiers with WebFinger. It's similar to this proposal but not specifically related to discovering server-level actors.
"},{"location":"fep/d556/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/d767/","title":"FEP-d767: Extend ActivityPub with Valueflows","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Lynn Foster lynnfoster@social.coopWITHDRAWN 2023-04-02 2024-02-03 #76 - codeberg"},{"location":"fep/d767/#summary","title":"Summary","text":"A standard method to extend ActivityPub/ActivityStream with Valueflows vocabulary, to enable varied economic networking activity in the fediverse.
"},{"location":"fep/d767/#history","title":"History","text":"Valueflows was started in 2015 by a group of developers who met around the concept of an \"open app ecosystem\". The goal was to define one of the vocabularies needed to enable modular apps and components to communicate in a standard way, using distributed architecture. By extension, the goal was for people and organizations to more easily work together to coordinate the creation, distribution, and exchange of economic resources.
Valueflows got a first version out in 2017, and started a period of learning from implementations.
Valueflows met the fediverse through Mayel de Borniol and Ivan Minutillo, and work was begun in 2017 to integrate Valueflows into the software that became Bonfire.
Valueflows would like to get to a stable v1.0 this year. The core is stable, but there are several features around the edges that are either not yet implemented or not yet tested enough.
"},{"location":"fep/d767/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this specification are to be interpreted as described in [RFC-2119].
Fediverse software that supports economic activity MAY implement the Valueflows extension.
"},{"location":"fep/d767/#vocabulary-integration","title":"Vocabulary Integration","text":"Note this is not at all finalized, will be seeking feedback in SocialHub.
The following general patterns MUST be followed to integrate Valueflows objects into ActivityPub messages.
The general idea is that AP/AS will be used basically for messaging. Valueflows does have some concepts that could be considered activities, but they don't fit nicely into the current list.
Note: The overlap between the Agent portion of the Valueflows model and the AP actors will not be addressed at this time, due to the complexity of mapping to the actor model, as well as the in-process suggestions for AP groups, organizations, communities, etc. Until it is explicitly addressed, when there is overlap, any Person, Group, Organization actor with agency can be used as a Valueflows Agent. In addition, Valueflows will include Agents that are not AP actors, and these can be handled with the standard Create, Update, Delete.
Examples (loosely documented atm, just for discussion):
{\n \"@context\": {\n \"https://www.w3.org/ns/activitystreams\",\n \"vf\": \"https://w3id.org/valueflows/\",\n },\n \"summary\": \"Sally created a project plan.\",\n \"type\": \"Create\",\n \"actor\": {\n \"type\": \"Person\",\n \"name\": \"Sally\"\n },\n \"object\": {\n \"type\": \"vf:Plan\",\n \"name\": \"Define AP-VF Extension\",\n \"content\": \"Collaborate on defining the VF extension to AP/AS. Propose as a FEP (Fediverse Enhancement Proposal).\" # or use skos:note per vf, different meanings?\n }\n}\n\n{\n \"@context\": {\n \"https://www.w3.org/ns/activitystreams\",\n \"vf\": \"https://w3id.org/valueflows/\",\n },\n \"summary\": \"Sally created a process.\",\n \"type\": \"Create\",\n \"actor\": {\n \"type\": \"Person\",\n \"name\": \"Sally\"\n },\n \"object\": {\n \"type\": \"vf:Process\",\n \"vf:name\": \"Draft AP-VF examples\",\n \"content\": \"Collaborate on examples to get discussion going on the VF extension to AP/AS.\",\n \"vf:inScopeOf\": {\n \"type\": \"Organization\",\n \"vf:name\": \"HUMANs\", # could use target for this?\n },\n \"vf:plannedWithin\": {\n \"type\": \"Plan\",\n \"vf:name\": \"Define AP-VF Extension\", # this would be an id?\n },\n \"vf:outputs\": {\n \"object\": {\n \"type\": \"vf:Commitment\",\n \"vf:action\": \"produce\",\n \"vf:conformsTo\": {\n \"type\": \"ResourceSpecification\",\n \"vf:name\": \"Vocabulary Spec\"\n },\n \"vf:due\": \"2023-05-08T10:30:00-5:00\",\n \"vf:provider\": {\n \"type\": \"Organization\", # vf? as?\n \"vf:name\": \"HUMANs\"\n },\n \"vf:receiver\": {\n \"type\": \"Organization\",\n \"vf:name\": \"SocialHub\"\n }\n }\n }\n }\n}\n\n{\n \"@context\": {\n \"https://www.w3.org/ns/activitystreams\",\n \"vf\": \"https://w3id.org/valueflows/\",\n \"om2\": \"http://www.ontology-of-units-of-measure.org/resource/om-2/\"\n },\n \"summary\": \"Lynn created a commitment.\",\n \"type\": \"Create\",\n \"actor\": {\n \"type\": \"Person\",\n \"name\": \"Lynn\"\n },\n \"object\": {\n \"type\": \"vf:Commitment\",\n \"vf:inputOf\": {\n \"type\": \"vf:Process\",\n \"vf:name\": \"Draft AP-VF examples\"\n }\n \"vf:action\": \"work\",\n \"vf:conformsTo\": {\n \"type\": \"ResourceSpecification\",\n \"vf:name\": \"Vocabulary Work\"\n },\n \"vf:effortQuantity\": {\n \"type\": \"om:Measure\",\n \"om2:hasNumericalValue\": 10,\n \"om2:hasUnit\": \"hr\"\n }\n \"vf:due\": \"2023-05-01T10:30:00-5:00\",\n \"vf:provider\": {\n \"type\": \"Person\", # vf? as?\n \"vf:name\": \"Lynn\"\n },\n \"vf:receiver\": {\n \"type\": \"Organization\",\n \"vf:name\": \"HUMANs\"\n },\n \"content\": \"First draft of examples, submit for feedback.\" # skos:note?\n }\n}\n"},{"location":"fep/d767/#only-stable-parts-of-the-valueflows-vocabulary-will-be-addressed","title":"Only stable parts of the Valueflows vocabulary will be addressed","text":""},{"location":"fep/d767/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/d8c2/","title":"FEP-d8c2: OAuth 2.0 Profile for the ActivityPub API","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evanp@socialwebfoundation.orgDRAFT 2023-09-17 #165 Discussions codeberg"},{"location":"fep/d8c2/#summary","title":"Summary","text":"This FEP defines a mechanism for using an ActivityPub object ID as the client_id in the OAuth 2.0 authorization code flow.
(An earlier version defined a full profile for using OAuth 2.0 with the ActivityPub API, but this version has been abbreviated to focus only on the client ID mechanism. The title has been retained to accommodate FEP tooling.)
"},{"location":"fep/d8c2/#motivation","title":"Motivation","text":"ActivityPub defines the ActivityPub API, a RESTful HTTP API for stream-oriented social software. This API allows client software to read ActivityPub objects, including actors, collections, activities, and content objects. Client software can also create new Activity objects by posting to an actor's outbox collection (also called \"client-to-server\" or \"c2s\").
The ActivityPub specification does not define an authorization mechanism for the API, although the ActivityPub Primer Authorization and Authentication recommendations include some suggestions. Although there are many ways to implement client authorization for an API, OAuth 2.0 is a popular and well-understood framework.
OAuth 2.0 is broad and encompasses a number of different techniques and use cases. OAuth 2.0 Simplified documents the most common profile of OAuth 2.0: authorization code flow and bearer tokens. Many OAuth 2.0 client libraries implement this profile.
The OAuth 2.0 authorization code flow requires two main endpoints for a client to initiate the flow: an authorization endpoint and a token endpoint. These can be discovered using the endpoints property of the ActivityPub actor or the Authorization Server Metadata endpoint from RFC 8414.
The OAuth 2.0 flow uses a client identifier to show important information about the client software to the user, and to avoid certain classes of spoofing attacks.
A common use case for OAuth 2.0 is an API supplied by a single provider. With a single provider, the client developer can register a client ID out of band using the provider's developer Web site or other tools.
With multiple providers, as with the Fediverse, out-of-band registration becomes untenable. With tens of thousands of known ActivityPub servers on the Internet, client developers cannot manually register client IDs with each provider of the ActivityPub API.
One option is to use Dynamic Client Registration protocol from RFC 7591. This defines a standard HTTP endpoint used for registering an application with an authorization server and receiving a unique client identifier.
Dynamic client registration adds some extra complexity on the client side. In particular, client software has to maintain a record of the correct client ID for each authorization server used.
This profile addresses these issues by using a single, well-defined ActivityPub object to identify and describe the client software.
"},{"location":"fep/d8c2/#client-identifier","title":"Client identifier","text":"ActivityPub provides a rich vocabulary for describing objects in the social space. Each object in the ActivityPub world has a unique https: URI, which must be dereferenceable to a JSON-LD document describing the object.
This allows a distributed description of ActivityPub API clients that doesn't require out-of-band registration.
Objects dereferenced at the id SHOULD be of type Application or Service. They MUST have an id property with the same value as the client_id parameter. They MUST have a redirectURI property with the redirect URI for the client (see Context document below).
Clients SHOULD provide metadata to help users make authorization decisions, including:
nameMap or name: The name of the client software.icon: An Image object with the icon for the client software.summaryMap or summary: A description of the application or service.attributedTo: The name, id, icon and summary properties of the actor responsible for the client software.Support for using ActivityPub object IDs as OAuth 2.0 client IDs can be declared in two ways.
"},{"location":"fep/d8c2/#actor-discovery","title":"Actor discovery","text":"An ActivityPub actor can include the objectIDAsClientID property. If true, client software can use the client ID format in this specification to identify themselves to authorization servers.
An authorization server can declare its support for ActivityPub object IDs as client IDs by adding the activitypub_object_id_as_client_id flag to its Authorization Server Metadata.
The context document for this specification is at https://purl.archive.org/socialweb/oauth/2.0. Its contents are as follows:
{\n \"@context\": {\n \"oauth\": \"https://purl.archive.org/socialweb/oauth#\",\n \"redirectURI\": {\n \"@id\": \"oauth:redirectURI\",\n \"@type\": \"xsd:anyURI\"\n },\n \"objectIDAsClientID\": {\n \"@id\": \"oauth:objectIDAsClientID\",\n \"@type\": \"xsd:boolean\"\n }\n }\n}\n"},{"location":"fep/d8c2/#context-url-aliases","title":"Context URL aliases","text":"Aliases are provided for the context URL to allow change over time with backwards compatibility, using a semantic versioning strategy.
https://purl.archive.org/socialweb/oauth/2.0.0 This URL will be bytewise stable, and can be used for clients that use digital signatures or hashes to validate context URLs.https://purl.archive.org/socialweb/oauth/2.0 The preferred URL. Backwards-compatible changes, such as whitespace and formatting, may be made, but no new terms will be added and none will be removed or modified. Will be kept up to date with the latest 2.0.x version.https://purl.archive.org/socialweb/oauth/2 New terms may be added, but none will be removed or modified. Will be kept up to date with the latest 2.x.x version.https://purl.archive.org/socialweb/oauth The latest version of the context document; backwards-incompatible changes may be applied, such as removing or modifying terms.New versions of the context document will increment the major, minor and patch version as needed.
"},{"location":"fep/d8c2/#properties","title":"Properties","text":""},{"location":"fep/d8c2/#redirecturi","title":"redirectURI","text":"The redirectURI property is an IRI that the client uses to receive the authorization code after the user authorizes the client. The server MUST verify that the redirect_uri parameter in the authorization request matches the redirectURI property of the client object.
This flag has a boolean value, true or false. If true, the authorization server for the actor with this property supports using ActivityPub object IDs as client IDs, as described in this document.
The following actor description declares that the actor's authorization server supports ActivityPub object IDs as OAuth 2.0 client IDs using the objectIDAsClientID flag.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/oauth/2.0\"\n ],\n \"id\": \"https://social.example/user/evan\",\n \"inbox\": \"https://social.example/user/evan/inbox\",\n \"outbox\": \"https://social.example/user/evan/outbox\",\n \"endpoints\": {\n \"oauthAuthorizationEndpoint\": \"https://social.example/authorize\",\n \"oauthTokenEndpoint\": \"https://social.example/token\"\n },\n \"objectIDAsClientID\": true\n}\n"},{"location":"fep/d8c2/#authorization-server-metadata-flag","title":"Authorization Server Metadata flag","text":"An authorization server can declare its support for using ActivityPub object IDs as client IDs with the activitypub_object_id_as_client_id flag.
{\n \"issuer\": \"https://social.example\",\n \"authorization_endpoint\": \"https://social.example/authorize\",\n \"token_endpoint\": \"https://social.example/token\",\n \"registration_endpoint\": \"https://social.example/registration\",\n \"scopes_supported\": [\n \"read\",\n \"write\"\n ],\n \"response_types_supported\": [\n \"code\"\n ],\n \"grant_types_supported\": [\n \"authorization_code\",\n \"refresh_token\"\n ],\n \"code_challenge_methods_supported\": [\n \"S256\"\n ],\n \"token_endpoint_auth_methods_supported\": [\n \"none\"\n ],\n \"activitypub_object_id_as_client_id\": true\n}\n"},{"location":"fep/d8c2/#follower-recommender","title":"Follower recommender","text":"A Web service that wants to use the ActivityPub API would define an ActivityPub object at https://followrec.example/client. This object has a redirectURI property with the URI of the Web application's authorization endpoint.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/oauth/2.0\"\n ],\n \"id\": \"https:/followrec.example/apps/myapp\",\n \"name\": \"Follow Recommender\",\n \"type\": \"Service\",\n \"icon\": {\n \"type\": \"Image\",\n \"url\": \"http://followrec.example/followrec.png\",\n \"width\": 256,\n \"height\": 256\n },\n \"summaryMap\": {\n \"en\": \"Follow Recommender is a service that recommends people to follow based on your existing community.\"\n },\n \"attributedTo\": {\n \"name\": \"Alyssa P. Hacker\",\n \"id\": \"https://hackers.example/alyssa\",\n \"type\": \"Person\",\n \"icon\": {\n \"type\": \"Image\",\n \"url\": \"https://hackers.example/alyssa/icon.png\",\n \"width\": 256,\n \"height\": 256\n },\n \"summaryMap\": {\n \"en\": \"Alyssa P. Hacker builds cool stuff on the Internet.\"\n }\n },\n \"redirectURI\": \"https://followrec.example/oauth/callback\"\n}\n"},{"location":"fep/d8c2/#mobile-checkin-app","title":"Mobile checkin app","text":"An iOS app uses the ActivityPub API to post location updates for a user. Because the app is a native program, it uses a static site provided by its version control system to host the client object at https://developer.git.example/kfc/client.json.
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://purl.archive.org/socialweb/oauth/2.0\"\n ],\n \"id\": \"https://developer.git.example/kfc/client.json\",\n \"name\": \"Kentucky Fried Checkin\",\n \"type\": \"Application\",\n \"icon\": {\n \"type\": \"Image\",\n \"url\": \"https://developer.git.example/kfc/icon.png\",\n \"width\": 256,\n \"height\": 256\n },\n \"summaryMap\": {\n \"en\": \"Kentucky Fried Checkin is a mobile app that allows you to post checkins to your ActivityPub timeline.\"\n },\n \"attributedTo\": {\n \"name\": \"MobileCorp\",\n \"id\": \"https://mobilecorp.example/organization\",\n \"type\": \"Organization\",\n \"icon\": {\n \"type\": \"Image\",\n \"url\": \"https://mobilecorp.example/organization/logo.png\",\n \"width\": 256,\n \"height\": 256\n },\n \"summaryMap\": {\n \"en\": \"MobileCorp provides cool apps supporting the social web.\"\n }\n },\n \"redirectURI\": \"checkin:oauth/callback\"\n}\n Note that the redirectURI property is a custom URI scheme for the mobile app.
redirect_uri parameter after authorization is complete. This can be used as an attack to treat the authorization server as an open redirector. An app using an OAuth 2.0 authorization endpoint as an open redirector could change the client description document to have a different redirectURI for each request. One mitigation would be to archive the redirectURI value for each client, and cancel the flow if the value has changed too often.client_id parameter to avoid attacks such as very large responses, responses that take a long time to generate, or responses with poorly-formatted content.name or icon. An attacker could use the name, icon, or publisher of a popular application to trick users into authorizing the attacker's application. Tools such as shared blocklists, reputation systems, and user education can mitigate this risk.The following authorization server metadata value is defined by this specification and registered in the IANA \"OAuth Authorization Server Metadata\" registry established in OAuth 2.0 Authorization Server Metadata RFC8414.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/d8c8/","title":"FEP-d8c8: BitTorrentTorrent Objects","text":"Authors Status Date received Tracking issue Discussions Repository Jonny Saunders j@nny.fyi DRAFT 2025-11-03 #722 Discussions codeberg"},{"location":"fep/d8c8/#summary","title":"Summary","text":"The BitTorrent protocol is a p2p protocol for distributing data described as a series of hashes and file metadata contained in .torrent files. This FEP describes a JSON-LD representation of .torrent files as an extension of an ActivityStreams Object.
A torrent file1 is an abbreviated, verifiable description of a file or directory that, at minimum, contains
piece length that allow downloaded data to be validated against the hashesA torrent consists of one outer \"metainfo\" dictionary, and an inner \"info\" dictionary (or, informally an \"infodict\"). A SHA1 (in the case of v1 torrents) or SHA256 (for v2 torrents) digest of the bencoded infodict serves as the content address for the data summarized by the torrent, or the \"infohash\".
Torrents are an open world specification: the specification sets the minimum required keys and their use, but any additional keys are allowed, ignored if the consuming client doesn't understand them. Torrents are encoded in a bittorrent-specific ASCII-based encoding, \"bencoding,\" whose strings, integers, lists, and dictionaries can be represented as a subset of JSON. Together, this means that with minimal adaptation, torrents can be represented as JSON-LD, with protocol extensions annotated with vocabularies imported by the @context.
MUST, MAY, and SHOULD used in the RFC-2119 sense where they appear in CAPITAL LETTERS.
CURIE prefixes used in this document: - bt: https://w3id.org/fep/d8c8# - the namespace created by this FEP (see FEP-888d), referring to the attached JSON-LD context fep-d8c8.jsonld - as: https://www.w3.org/ns/activitystreams - the ActivityStreams vocabulary
A Torrent object is an extension of an ActivityStreams Object whose \"type\" is \"Torrent\" and whose uri is bt:Torrent (or, expanded, https://w3id.org/fep/d8c8#Torrent).
The contents of a Torrent object are, at minimum, those specified by BEP 0003 (bittorrent v1) or BEP 0052 (bittorrent v2)2.
Torrent objects MUST be JSON encoded, mapping bencoded strings, integers, lists, and dictionaries to their counterparts in JSON.
pieces root in v2 file trees MUST be the same as the encoding used for keys in the piece layers dict.Examples of strings that should be encoded as base64: - the value of the pieces string in the v1 info dict, even when there is only one piece hash. - the value of of the piece hashes within a v2 piece layers dictionary, even when there is only one piece hash.
Examples of strings that should be encoded as hexadecimal: - v1 and v2 infohashes (below) - keys in the v2 piece layers dict, and values of pieces root within file tree
Additional terms may be added to the metainfo and info dictionaries, and when returning to the bencoded form these terms MUST be kept in their unexpanded form (i.e. not expanded to full URIs if they are terms from the JSON-LD Context) EXCEPT for the terms specified in this FEP, which MUST be removed from the bencoded form.
When bencoding a Torrent object, the inverse encoding to that described above MUST be applied to strings: hexadecimal, base64, and unicode strings MUST all be decoded to binary.
Torrent objects MUST contain their infohashes using the following keys, depending on whether they are v1, v2, or hybrid torrents:
bt:infohash_v1: v1-only and hybrid torrents MUST have a hexadecimal SHA1 hash of the bencoded info dictbt:infohash_v2: v2-only and hybrid torrents MUST have a hexadecimal SHA256 hash of the bencoded info dictSoftware that consumes Torrent objects SHOULD verify that the infohashes provided in the Torrent object match those computed by the bencoded form of the Torrent's info dict, and MAY choose to not process Torrent objects with incorrect infohashes.
Torrent objects MAY contain the following keys:
bt:bencoded: a URI to the bencoded form of the torrent.bt:magnet: A magnet URI that MUST contain the same information as the Torrent object, subset to the fields that have corresponding keys in the magnet URI scheme.In Collections or other circumstances where presenting the full, materialized Torrent object might be expensive, implementing software MAY present the object in an abbreviated form consisting of
Required: - the object's id uri - its infohash(es): bt:infohash_v1, bt:infohash_v2, if applicable.
Optional: - bt:bencoded: a URI to the bencoded form of the torrent. The linked bencoded torrent MUST be equivalent to a bencoded version of the JSON Torrent object (see Bencoding, above) - bt:magnet: a magnet URI for the torrent.
In circumstances where other ActivityStreams Objects might be presented as a bare URI, Torrent objects SHOULD be presented as this abbreviated form, allowing the possibility of using the infohash to dereference the torrent contents via DHT or other means.
For some example torrent with arbitrary contents, we might expect the JSON Torrent to look like this (assuming the @context is supplied in some outer scope):
{\n \"announce\": \"udp://tracker.example.com:6969\",\n \"announce-list\": [\n [\n \"https://example.com/announce.php\"\n ]\n ],\n \"created by\": \"Example Torrent Creator\",\n \"creation date\": 1724037213,\n \"id\": \"https://example.com/torrents/torrent123\",\n \"info\": {\n \"files\": [\n {\n \"length\": 5261174,\n \"path\": [\n \"tentacoli-15-Tentacles (Versione 2).mp3\"\n ]\n },\n {\n \"length\": 2778997,\n \"path\": [\n \"tentacoli-02-She'll Never Come Back.mp3\"\n ]\n },\n {\n \"length\": 2333412,\n \"path\": [\n \"tentacoli-03-My Son's Friend Is A Champion Pisser.mp3\"\n ]\n },\n {\n \"length\": 2562622,\n \"path\": [\n \"tentacoli-04-Summer And Winter.mp3\"\n ]\n },\n {\n \"length\": 2796486,\n \"path\": [\n \"tentacoli-05-San Diego, Yellow Cab.mp3\"\n ]\n },\n {\n \"length\": 3863849,\n \"path\": [\n \"tentacoli-06-Happiness Is Having Two Killer Whales As Friends.mp3\"\n ]\n },\n {\n \"length\": 3696387,\n \"path\": [\n \"tentacoli-07-Too Risky A Day For A Regatta.mp3\"\n ]\n },\n {\n \"length\": 2583746,\n \"path\": [\n \"tentacoli-08-Sorry, I Have To Go.mp3\"\n ]\n },\n {\n \"length\": 1924024,\n \"path\": [\n \"tentacoli-09-Scotch For Two.mp3\"\n ]\n },\n {\n \"length\": 2463949,\n \"path\": [\n \"tentacoli-10-The Killer Whales' Games.mp3\"\n ]\n },\n {\n \"length\": 1783901,\n \"path\": [\n \"tentacoli-11-The Capture Of The Giant Octopus.mp3\"\n ]\n },\n {\n \"length\": 2965602,\n \"path\": [\n \"tentacoli-12-Two Old Kids.mp3\"\n ]\n },\n {\n \"length\": 4319196,\n \"path\": [\n \"tentacoli-13-Tentacles.mp3\"\n ]\n },\n {\n \"length\": 1555839,\n \"path\": [\n \"tentacoli-14-My Son's Friend Is A Champion Pisser (Versione 2).mp3\"\n ]\n },\n {\n \"length\": 3177585,\n \"path\": [\n \"tentacoli-01-Small Town Pleasures.mp3\"\n ]\n },\n {\n \"length\": 2877921,\n \"path\": [\n \"tentacoli-16-San Diego, Yellow Cab (Versione 2).mp3\"\n ]\n },\n {\n \"length\": 1079679,\n \"path\": [\n \"tentacoli-17-My Son's Friend Is A Champion Pisser (Versione 3).mp3\"\n ]\n },\n {\n \"length\": 874993,\n \"path\": [\n \"tentacoli-18-Too Risky A Day For A Regatta (Versione 2).mp3\"\n ]\n },\n {\n \"length\": 3951350,\n \"path\": [\n \"tentacoli-19-Tentacles (Versione 3).mp3\"\n ]\n },\n {\n \"length\": 4358356,\n \"path\": [\n \"tentacoli-20-Sails.mp3\"\n ]\n },\n {\n \"length\": 2870766,\n \"path\": [\n \"tentacoli-21-Sails (Versione 2).mp3\"\n ]\n }\n ],\n \"name\": \"tentacles-tentacoli-1977-ost-soundtrack\",\n \"piece length\": 131072,\n \"pieces\": \"KHWQgYXgbUMnxhVsSiMXnmT8XKpeHwMOnVFCk+yZuDQegJoJy+JuxOxmxOLM3Ah0uGR5OsGgt3UmkZFMCCUNZkfa/MwHc5WE+PMedjx7sbNcNq8aBOEt4+m4qsC4WXfU+21w58CpXdIw59MVvhTpvfWSgE/zfGjEcQpMe7bpWgP5cBIVRo2uF8TyU7GhgHxmJf2dAGeNb5qxsNiEFr/aLmFc9S9+ImE+DFBf2xVfWommHSE+RiLUKoR+cGlFsuRd8nI3TptBqYhI/u+2XWE1NI//egRcAGPewa9rMQ+MYPtcBQdAxndhysx9eL4AB6vPalSaYiFrJoSAJ/tcR8HRNGTu2HVb4Z71gy/+vo8kJDJBXZA+YN/1Al1F0CXucst3Ksh5aCpOnFyJfnfWJh6LZCZtaq42eVmoco6p9U1YYJGPSnwIWxnZXgikExWlC2O/lGUUdTzLBMIzbGxZLzknJ720C5n7zfac7zIoHIY10ga4yjemeg9D/4J+NUIrTVc34J35sgniluu98Lk+VpUKJ+WCKWI47Ttv0rJ3sDobiHSkApRYt/eRmrr4wo8IVFnyWmL1HVT+Lwdl5zKnjv3m/iqmPgytNAlZIagzwAJYFFQBaE+7TcKVY65Qw3TAEEMbALv1/ukB99sClOK3FVY1IxzTJPEnBUHjq7grZUpYb0elaT0Li8fw8fpcJY9Guctw2KcZ0cXnDCZb/8FzxWW/s08uNyzAbphnHi1F5XznhsN4GcieykZD72fI3Bln7YlvtDcHipabxGOX3c+WzHSIK5+YNDKqLLhH9hEJ7orw+ViSyWABEgI4esSogymT/mhKUIhhFhRY8OHWM/4d7n04dvfuGs6NawkMn8mDK5XnqcdVxZgGmF8b6+DDTkqQMpn1t5f8spa1NhHglxqPkfVvqqLs8aEOrGE3VfBmqmaRCngIGQTlAIVo3hcEd7t24896xifWf8AQLiqfKbAJXRAPxQG4dRM49YW5aUeaWbv8vt10WlBsfVBVTL2yo+B9ePWvlj2QW41a84v3VorndZS9x34RVNKqLRAYmXt54u3ZHu4BtaHjZovhbGTTPclAF4N5vQ3ymf7Q0UCyagp9FOwLSmj7d64SvoacPN8Sp4fy8Ylxn69ns+df3wkhwAqHwYeY0ys3JeEER4paBuMyetcX8IRYQMc3zfHYBi+IA6Xp87Ey56I8p9e7WVGyb/kt2krhpaN34A6UgXIepU9iJ0npY/mzKpt7uMv250EnoaFXYscdG4yLqp2Q3sGpJOeuS/csBCiqYPR71lBQACXRpAthXNcxh9Yu36jbfdQOxNgmtVlsx14/TtjvhVJUzPXpjFDgds82QBGw6xPhTSZLm50diF3X055xbvl2aEdqZgO4DmFV0C64lkQ9wYg7Kgb1QpeE18k0SPaYP7D8TONpq6o6rl5f8ug+I6Kx2vlZxEx53dyXTg0BYv8a6j40xdMR3IonlBLlkrx9BvznuR1qmgFJ5bgGRsky8qNx7RP+HYZpzYkPMPe8HykGOHdN/o7I81HlWiPzj0u5lkEIoaY9V6YAXcDNGLAR1X0BF0MtR6sKUe3FK8NbVVStVECcwz9fQO7bO48OuWxNDJZjK6qeZ6CmwZUc2hZkfLAmN8DmrRCbQUuYdXblQvEnIXYjeyB1kL0cigIN0eO7uSVep8Tkr7OPr6ZadZqtHLfp8+743Y6GhCfqq4dlcu30kPUwOqJc4ZnF8dIVPb336lq2RVLARYJyBD0Ut8gQQLmTBYoNVxNhI/ygkDBRjbK6TCkO6lKNuVkJitdiRZO7KNvKmHzV1sXnS6BYOAvfmyaLm5MG4sxKQtLww0mmvh77fpIAFuZDY5nqYHyDYxOoeoL0GCUgYJPw/7lNj6KGMozdW7ewAoyUp3/n5n9WS4sLx8hxj+tUf7jN9oqrgPZJb/IlkLhqa48hJTmefbcSxCNqnZrld01Lua0qmclvPESm3IOn8ZDwcZ3qTL46nt2zCm1ovuWMBPS5RLuVBGYelfgvEnUac53P13/TiN9dZXHlMOsdzkqwi8Q8npZoaZvvjTOPTXYQbH8nA8z5e7yGDAL8PFsLOakKUOMZF3LxiHl3Zqefg1yYgnMtVjvER9T2dQVmxVolRokdZx9un0tQJL2h/fCAIIpAGClsjDOv4rzxEswAXLM265U8g9LDWK0o9+dlhLB/HvDBTqK36eDjQXpqOGU4R+8GEm4SVN0Ws0MCXQoYdkdoFcddwptuyKoywdfw+UlGy4aOXZjuMmHetS457CAQnQTjx3YEvtOJ5VsMlj8UndFnOXtOdTABLe4gUjtPgO83KLOg6Zm7JsIw/Vo2IbgX9R/uxeyqDA56ZV43V1KKdFV6xUgU+pcFQQwBTiV6WtF7jQ472NgSJULEhrl4CltpBJ4m/+tjQ06tom146skdaeL8xkfrjPqm74gYlKOqRIsiWa04Yiytw110DIOnvW/SRCnu99hdCkzIE3quV1boXsmDkiYPBUHUIQeEJU6a+qxXozGY+ETe5udc287NJr1byeKRPjg5DYbpgb7xOsBZzj1D656iQeteICu0Ylrmx4XJbsT/7y0VjCMi6bwGUcnAZ/mNZDPKJPcQBReOCdnXp7716tYw2VxTKQpHWWkxQkCFtgH0z8EHuyjZscZD5O48i9qrhCMs21vVHifLHphVSKRX2PhdL4l943JEg5O9EkGnI+Us1rD2voISp+f3VSFkw4owBM/yuUenUU6IN9sdgpc/HHeIAqEFrBtozkyWQIhS4kUeSLmRsmM3e6wAIztCLSb/u9jSir9qctk/xVbAj4sf9Cdc2LZM/mMzg8x4wTbrZK6B/OByTR/SyV89ZAFWxFsPbm+hWZx82B47Vhp6fU480/C38aaUCItSQAtT08VBF51kXQ1BDZ8cqsmbf4ErbBYi+rPXhSf5nO/PuCg6ZJmfOeDZW8aRw25mdzoKpNVXbDU64BlFlNrrgc4y/jYT467jU2tzxrt1kBplE376xocagNK3HU8SEPCC1wFEOthZiVOM91aCdkt4QznjENfZqxARiKT+EddA/khZ+P79HU9O7Tsd73/z21JXsqAkF/t4ZhTkIxDxGumwuHMuOxiCej0eZN6jZUWsXo9dqueGgjl/odcqMXq5WyXVZHyZUsU0/1vXUPUlsnMoIq53DqXa7jqjHCylFOxAVJHtTarJzvQwCD+cnzBlSlQRd1FOgWBIajwuLvWpx5rWCt497Bp+Rn4z31xMhaD/nSsbGJFIAcV1YtUAG0TtR6DXFVLYNh9ClN+qaACAxLV1yYd2t0XPw1/HflEXwImPeRrJJk6gCE1mib6lMrawqirF4tD0vVbGotRWb4oHIaDS7GTlVFPpDvKopi1TMqBsPF8go4nwMg69saPTm4U+/KSR6myU8RSAc+fyMdqTPy2A3Yv8RMp7UEEWFXEcC62PQnDdKTzhqXTFSjybG7EflrsFbeUmqeW0LShCDiZuvWPX07I/NfM7sKhoX36J4T25Q7fMi9VZ2j11Muf7SG6hqFcgvICtfOzXpU5w9WnIgSfaGLXoaC4NR4dExSSPoFW0UyluTcFPNsAN6Mp+dzwOuhAV1rS2XYLW7wjFutpW2pUaEBW3YVR0AC6zWsDuHCQIeAAPUQcaTLgVReTIiA4dAqo1J/fh1/xofGJlf8Jt8UK0/3Hxs3cfTAguSqIGRbxm+zzty1xeqIkDIXUbwxEdFb8+3YiftPn1zA2BTo+gl6NnGjE1WTMctmeb3AttiH8QvQA8taGESNJAJebioxm3zHpHywG8IGFDMI7r5BcHxDKGNAdGisYdXF2YqpZPATfUvUspU+em6wK1RM7eeo03LnTnI0wzpChk8eMDTAIqHy9nfjgSnxUTuK+8CN+6bYFkLxBf+u+q5BIVr5HYStlXuOAADvuuqcOFsH5myf3C8Ma0l4j3dkP2MuiSGExjOFrwB5Fzo8mlJ9zr/bjVzcH6zXk4tJjzb2onaDZItWEi3NkcICF97EHpgFpiknSL/M+C+qq/i6lXs/KCEOuYhipw36uerG4VAidCZOlGtlr3rOHUhycHDBB6zd+5lu/f7QHKR6H0w1kGTiQ02E4y6o/bElg6pxwpZWD9daKHqBhEO4tOm0ca7R2XlbOqVviUN8tXCj09fQ7Ojrv42ClFjz0utxXmG59IvNUtViNTw879Czh8pKMAbQIzrzdHkjZd4U6jF7vVGHCFyqxWVrN1EzP9kDTM3J3QLsAHDIFs/h0IJsHDEsYvSWwgqonIuw+mF1Xnj0hqgKz55v7cH5FRVBjLTAzKJrh6uQTIf5A44k+KXgB/4hlDSd8o8tkTcOAKQgRWTvdf/uwnoH95FjBhqoJo2nZ7kxZqvuC5XKVFw7pndFClCbnyZ+XbLzitYwHYn/3CDXamfaCUXp4QvdISPRaWrnLd49IqGr5i3mYeW+mQ+YaUvQaNpF4eL2Fe4K9+QojrzjLvmDRmQFlSdNdrCUw8DsZg6DOGLbkW3CX/Ov9XMugrIZPoL2z07yffOgHwOV8+JPrHEBnqmRZb9dyb81/TU9C0ItWMuXbS/4e/AFZHK1LGMTRuJIWY68oxE0Xic4lYE8fsKV4zGKjJbjkt17hqBEBuLAkkKBtrYLrglWmThuJHqLjaQb7VfjedjNFb5Og/UkbSX1G+Oi5B9c0O34rD30p7eVXE6IFj97Kme5BduKCK0QAXtFOkSnjzhr3VMkjgdBW66Cw/SwljLhTYg5mC+e5Ws3vVwH7t1rfGnS/JL4bkgCVNcW0xsIniUxjjoD0Hj9LENMmeXBFMv6KIdD4eLVMCC4MvaEQZtwmt5V9V0/Qia/FVj90cjIYKegBP1fPBtksQzi4LC+/DpPd3cjkvdPFXZ4Votk8Begx//f1L1RVQX/FGvfJTb+LoXFrM031Mab/hNwP7Hr3gRpXZNam7XQ73dOsv5lx9ziAAr/SNufkcgy80b/rTmpBJX4VNZAESGe5IoaduSZ9KXkt7VkhrlD3mRQigOz6g8XiPjFspwB2P6AooBxZMp/5Wbi7JNbXBj+l6ZXQx6ZhPbVCdlgeNkl6+kSrz6KZTZqVnamCteJP4YJwZxpQOALFvUIK/CUA1Hx/oD7rHIuyX7kTEpltdksUhmYRNQ0dy4b64imhC1fL/3HSPpnVH1FopJ7GRVNNeNMnb3h7YJCZtSqXABADSnxYUWaMtQjFPBDw6ks411fAkQnGFeitFLFhmO/+2uwXb2DfXCkciCLK+ZEUadhBKbV4Ptu/pC+rg9hKRNKaF/BlHXR43cj8iYd+HhRz5mMe5vBXt/LOUr9xOREcNFbKb42IzHot4EHq3/VKQs2uKW44+nlJDys0k3foSTso7UA3ZTHieFKqNwgoj/rkZQZsMZF3byF9Wl2CeP5zS50x1oUHRUyqflAOxzyT/R54n1B1ATcE9yxFy1ZAIggnvBjLK4+8QcsZUNpF4DGKaQbpUqY/Ex3URhmyANQysVrtehmCJ1jr0ZkJV7eJ56SaxmYe1VPw2dbX/4LjjBThtDMOQGqPc5ct3MH/t2no+t+IfJXOcbdV8nzrCYh1jo/HADSmJCo/rH3dwTk2HvpB9KnFjfFLdwhfpvOVjqxyj26vYul0ucS9U9Or8LbUSIfi9SoPIV97G2Z4jwCctZmMHDLz7/9N90GSjmYkiiFYxJ9NV7Zy0IK200RT5+ywmnTHqQBqbB0NZcYLHvq6fBVOHuHAaLgYjzPkXyajReJsn/pJcZmXrR+1JypmvEevbPJtYDPoIomhbJOI4q+DDwieXpx35pyT+8BygkCwvZlOUnMNI7DgamGj6zpkqLBICa2ItJMjRnaHCnbXR4bvJ5gyVhOT+9PFNecjAClYyERpDh1ydFMlHNjmhBEqHyz3Uzb/YlrG5OEzLjz1qf95/iMAzfbiOhjzorbtmCmEgl7IopWvqHmEj4b9Z/47jrHn3PeaZPbHihsq4pGnyr51NCPl16OTSTBA4a5u+g3m8rnL+CMLQ72cSTNLLEK3V57KIeaIMpzJg0ZYb0rSzNndGPAKdS5i3vC9hseaynsftvaUz8AyViUtWKVgU8r34cD5/yYynumy5OisB0M7McthNIQfG3wTI9+OgM1wFB3B7+M8Xi7ttziUfGKjqIwo8PF/F5FcR5jCDxZsQYQ+1hMqVeNR2nCUnAupjMvxF7KJwwC4NNbYgphjDBz6o3FITLwp2PTqoQqOJ+lJpUl5vb5EXFNfsAUHYrp7dAwjVYn8y3otaMj2ont854tXK84GWtCe6BkWQ7kmp0K3PCvcMa3ghFmLoLr5Xk3RcxNtPXt/L6rFja3olrH6S8mNbtprc2WK/sBfI0XDKp0U/pqKd9E1Rt0X++DEvSBNucpn98/phsFEo6X2N+L0e+QyncDbA2RaDk7yUiC3Fm5xwJSNSS5L4zjth7CymUHVapZAvRgt447kta/KOc5hfEooL/axRzfcBmf/6APQ8Ot72+DHFyZtpIS7bajEnC3LJRguMoJnS32X1tuVOgeSOys6bvamq8C8EiACnoBDlT+rmIeIAPLRhFtEYYQDy/5ze+BfhH3evAAzkemPMEYD5hgV3Dy/HOrXDEV7mHt+2bJ2yjwrY0EfbEZmUY9T+bFLKMLm0DPRxl2aWsKLF1vUOHsqGctPp7hQqaN9zbKDIjpPf8ZgwAFSN7L3Nam6rCCc38VPogDeLlRB6YNB/anWhvUkMc/aB9nEwf7bc4j2FxCpqjqUP7BIhG/rsk3FBOBTe/uKk/TC6XOoekUmztfLpM64J9q8r71VVmuJ4IA0O/f4lw8tSyKJiwBbgeh87a+9Uo0FO3vJVVzH8bMlxQKcNAG960fXB40WMrEB/YOA6bbhUEjZVc/bqHCYzf4izoPMqJZssT5Zsy6r2aQIAOFRyXtASG3nhD6urIobLUhOER5VveS79qQI69zUMsZfLF2Gv32Me/NZv8pEoHIqQAlf6NAyCvV2WtlIcYde/JNzLgE4NQwJmEgX3b8zuK+WQHxCMIHqSvDPTxdIGrNn/f7G5wBbU6W06yHNAEIehFhUJHE2OxxcEppE3NBJgML+X/DE8PqOY6eQiWBbS4yGvaeF7gvhuidt4djaeV+8STxL+/B9pfv0Sf/BkooAM+igWPvmDGQZYahM0hyyO2Urks5rHC8HeOnAa/qaYnELVY92qWmOvAZNmlYFIbAM1zj2yZVDkcBeNy72c2IAlRGJMmBxuUJTGrnOY4UiRBsiYVM7n8Nm8FUwIxAv/PjICGS/kJ/21CaW4jH3nkycD7MkbgY/b+cu9DxUmE6dp/6JclZ9ziu4e6LgjfAF+qkmRJ8dIkvrUBZpKhHQt48pj/eK8cgBbNObIuKonClHVtcQXTTl9/RFuXS33nGrP+EN8M0qfgYm4ObCKpA47TA9I4xCCBKbyUMbc80JDLuJ46wXEncUVwxoJlRxHzPRybBAp58RT2t6AFFTbQsWguFDE4h6Jz5ZXQxJ0KAU2smWCpVxNuj1cruqPU2Bg9FLK+PUprcAwKKrrZ99N/5LEJc4iM6lLic7s0KJtYnHevJKqRrK6SsNXDTdWKzsOjjWoTMTFcO6CaJHbWgzgbrjEMX5yznG+c6epxIEe4PXDoKc96hlw9Quk8pO+s7bwCuiJOdHULK0a95hnwik9FpOJ20lLPOBcjtUEs6QBbbbJNiggg+N65uP9nIKz2jZvNNkfihuBrq9lmldDM/zrDhS++rGI83Hk8uNd62E42y2oRzvXksYU9zZLfj6r+WgGKXNE2zdSrppM7seqEvCzFrYTh9VF4d0mcP8fUP2NjQaSouzYvcgu4njLNgf9NI5YGAv15n169Qn2j5jDN1GCsUItCW66Z246qOyZadtFWv5OJhI/yfU/6nQ+rBG/YJj2MbXxZtYqUiC8HFdzuIsMjFhL0uNHuVS1JYVkcPkZ3UKMB4uE440tF/TTV5cPdjGDdBXgfzoo7um+hU/EN3Yh0VDUhMDVhmwXuTw5QjKMu8QBRntbY33QsFtqNUWM5JYsTmZOQoonm2gh7gkEc8mPo24DLoRKp1RyBA40d4PncSA7e/usX3LRUE3WvNCF8A03je7PKEHpIPuwBaDt81NsvnX3uoZr9MQBtDBw4/D0x6OwH/lXtnHb/u4HAL1AEuXHqCpguTWXOZdoPxuiZLjRHEHJMBfCBccQfitxQLridrHMr+cKskYzlvbFDToFjQVUGgMZdE9Yl69f3NHBunV4Xzrzn9z5uFa8iqOt5oyqpZSTC6iim7QkmDis56gksc7ebRN5ASr2mNg2/f0zblkJvohbM8Us2m4lJC9MAnryMxanR1xJg6CDD7Jmbd8pqBHc9mQ9DyC3XU2ZvN25+tqdPVy96cmZ9Ii3p7P6Hp7c1gDenDze1QSTI1JuDsZfI2r0OC7Ot+LEnd0GmmRRXhfXjgkdmWqGhwxfBp2FVJ1vfXyxA3+MdXsowpUKOHga7+I7YKtHzQvR8bo2CfbXiriqqxDmoRR8+gVlqVl3JBUIZX2gtSEoUyz6KjlyaUmzv6UOOz27cFc7tiY6zJ7i8hNdbAZRAt3/ge0i+1f0vNXXfW3Ur0xPlurhBWw58jf+S/lCVhqTbdzk1gTCSX0YNGYbXSAOe2rWbbQ9jxXxt/fSHVHYfa94P83yF8+fCd7KdDe94VeeQqg1fwTcDu+6A6udWrrgey1SnwqushIZfXFR66RSmNEijZp4lFIzkvdRpBzRFlLhTXpLURjCE3ndnNbdYhMqupllFjareSAW4hAwgYVbM6PGXweMh9UAuQP7B6z0Hl/hWDExb8ks71aWxRd94F9NOxP7pfDPjBWyTgBREgdJZ7PBFzl5SoXbOYZ1pBuJYEavd+kpYi7vw9scf2CIJQdgWXS+Itq9agLJhwBcoGQ8bpdzK2Ckc4gT6ZCASu5Zvxx9Yp4KyNzY5+aUmMbm8tHysTCsB7958FO/gZNrbpHSfigRNfN8P3aP8JZAOD0M2nk8rIeibC4kVtwRitMAxr/hk2jtSN9MUKEJDcRcljU8nfo9gV8XEg+OgyxF6Q/XTkRn7e8z7Qxj56oH2KKPi7GVik9KCC1O+9KDVA4JvJG8GeI4KqVGekL5as1zHTpH/o9/SgNUhMGch4eujMWI3gnEUnzB0raIJGUBvnL1ChH0SceVN5CV+Tt125pdbWdH8rptKlphXXthSxPt1vUcjydQfIMsWVLvtYaJZagmwpVqdycq2MpiF6aQIkGWeOoJ8gudHiTB2YbzGu3yZEXUV7JTfAW2rTR58ghg6ywruB8O9bjBKAOmHi5vf5sT5sv5pN88NLACYbkyp24DbRT7krw/AP74mhgxgrXIQ4j5Sa8fp4D5IFJcx1EUe/xYTGLtKaJFn3U82MkHGIVvuA7NHa5zockqNiWxf9YTSOKhhvdKa4VmpOS2tEmwfEJk5wqQAO8TKBnzdyR67+8jWmSH6V2WfVXw8PTibu37enPwrYsYwz/SKKOeKaJVWvIznmdBSFVJTyqT7pjDN5a+6LYneVjz3cv+BonlUuktHbcpDcKVXak4eU5nKmJ5CM/hCenf57bMtWLbKl5uGUBwDLqmkmxaNzSC/xA555cqfEbbYe2k89f/mLV8dMYO3GTlxKQohrIzo6kn3yP7wCbxGsomyteBSaXVIGTUECwe7Wq0Ls7BQA9wgX8H8Aj94DtR1rERRyOgLQUXkhiFkIM4bc5jg8ksdHKejnOlgbu7rwzg+A1hXymstoDKPvMcLjm1VHb2jnGp3pFWeNsA/FxwktZwG4cXSKVexIGca8CIcaRU6WCFazbkZ/wKLl03ou2TNfuphrruuoknRTLeH8oTgTgz0pSxm3yyhGTfqcgWO33rOWNsOthuJ3y5mTYdRTM05FqFZJr7ipZq/4FPBTbJeFMWIJjKugm9WWjg6JzBJpeUEh3MA9MMGBDDIakXAQfbgv51a3FrSwAxIu5qeftNgTQYjnKJtXsPt3vDBzgPsPRlNili3FUDNCJC/mnJufpPrgpc2fsqPiJ/2D4taVBrqElCmQjDt7VxRAKwvxZjl0NuIl54UODInCvk2kZvhS7yr5IUq53rYXULB9dA6MZEE/yCByIOpEzZgusV0ehLyllBrk/9XtuFuBKcJSn+sfjVLECd4e39YNlp5KHAfUPaEKMasPs5dH+npmHmW/2YGYQETeLE5cka8Yi+/Xahw+vRzUYSenHnJtuUA8a5swzEKW5EVw2ypk1Y91/NtlyO/Fr8jE8gz/yqdQzggrd4/s5KoP9S1sFgwhE+VfT+GcLUbjB8CLVCJymitZXJgGpOcfeFVxQd1cLG0DyWtcpDu8PsrzHKclY02mEATyFrefUSLHwJwoQ8Vr2gcXA33YmKOkdAC+vhilRW1Red6HBs79YQSfQuOgapQq3ZR84v1Um8beZ6G6Wkk6tPv2EqwyF6fDbefRTW/ntY2mUQaxmPUvTGNZQWTYt/AR2tucFDw7y8vN7oVONUKYeoVPcvl55Lc+Rd613Gdc2GyJUpwdKLjlkET7MmC4sX7qpU83WTrhnllwuvd83nYme5PLwqmLXdgxULkq2hWBTtdK5KHaNkSOkGBaN9rv8GOHsQWfkTcbuNfwg6T2MEdP77i7VVRrTiLr+etu1NEOuNh4x92n1aYwsLOhHaawcE9QwdDDZVF14rTBruI5jlWwSQg8oP0rlBiRTzbmpL1/QDPmOnXCDp6kxlLcAUGx1pk1qwBu60ePwvxSqhBvsDvplbZED3AHicX6CaHvAk0gicMn81CAIo8cSdBX906qM+ro7rKy4JIYmBgx2UMzqx+uO4gw8Ci1NBpVkSvHRCzh0rQy9431RN8rm4xNRsLwyJsh7AgW78divbbhjTZNyzBGJsMbED7mq/8JLx1ygcmi9AJjYKoGo1dFn6K8UF2fibLeZ+pStJFOSTtj3WfUAdpjOQ1pNVZ5XNxr+G+pW8YOohY4WXZbHFVgmPJeJKhPVFTpq7czskpx6TrK2iLeeX+TQlqwlPZDIt6nv3sQomHJqZ6ayDmETEepv6bBEvq4CnhTYj9YIJO22A1///QmUCMLwsa5fwXKtAFqjBUx7xF48R3niNkFFKIDK9g8UeepNUziMqE/iZL8NZSeMUHdHB5T65o9zWZmZv23MS5TR0D9LUdqT0hQYU0n0W3AL4fgKy2eS4AApVzE/B1Xva/g+cASlVvpJbek2/ATNvyo/pVnwurqidIcvvl1/+jp6x5caZ2G0g2a6yy4zAX2lHFox8P0GRTPFqi5uTfOlzUHgQ8HQuW8bUVKaojUv3TfJEAaGHNBimAsi6ZPToaLAPCgs5CJiYEPMdVuJCQj5cpo0WiLgxJ6jyRBqa3RRn6F56bGpatNt6lY25G9PnNrLJvpvm5fdZPTO4P+C/qxfr5OJMQgUWre7RaTIXSrmxUpND+LMgG0MdbTD3PP+JaTbJpnbSop3lLWQKX03LlhnihQHN/aDV/FyfAzocV8IiKoLQ9qUwOZDhqRrSrUGG9rWtJn4Iq5OocKzMkiKiMW4ULKhtF7h10thDuxoxa+j/eLmWPNxVXW59mSn2LE9PsTtNJ4BGU+Bd4f1onHrl3l+RPkV0r3B+Sl7AFcFopY3lOcXBSo+9bCRRLpCMzz7oEM4qk9jOoLkwFNLtcJadgugvOuA5oc0w6ccB9KYtaiuFy9aSeWkvvC53EqUfBTSSdicVbn4fFzdN+MsZL+VuRW+FCg8Tv56b5CtNpbOqqRJZ7vy8MATlfFlRbB2Ob66CP4Okx+I3+Cp2TcQ7vWi5Qfsy6dP+/EVSlWg5blfR7qdhb6WbuhwSn5bUXGp+IlaPQfl/k/Dhix5f4U711APLhYhIchQ6GY+yjyUDnXwrWvNv2KUjlt9bFCtdUoeAzG5N3oCF/IBgINbXuubWJpZxJX0MZnSQ+p0LuW+3rjEx6DxHAPjGMFRV08hRYSfo8x3fmvO95JuOZ9ahTz/btV82nvPIfG2mzhg4uqhSkSwMVLkAu+/LYvNv+xZjln39wYRaCfFjc5Ebj5ST8yDhWJprmVrsqVu+ZSpiTM/o5J9jScYi7+PYGgjf9hQg7qdeL1rWjwRkVwM2E+1XrU0TGdCyTmP5lx8sylD4+yFbOgNaE/Wlo8f6fe+EpnLlb8VZ9SJt1HPZTV8hPQKs7kkX2hxmmhlAh4Eo9QMzPSy3eJ1cP9W2frrHB0McjM+1DiO1/hCjsMBF2aSEhWvLxgvKwRFnn2Q3o6rF0lOWo3zriVwG0eXdUX9vAsTf\"\n },\n \"infohash_v1\": \"1ad02871c78eb1c2934f46de0c7ffd9ef9ee4083\",\n \"type\": \"Torrent\"\n}\n"},{"location":"fep/d8c8/#v2","title":"v2","text":"Expand/collapse v2 example {\n \"announce\": \"udp://tracker.example.com:6969\",\n \"announce-list\": [\n [\n \"https://example.com/announce.php\"\n ]\n ],\n \"created by\": \"Example Torrent Creator\",\n \"id\": \"https://example.com/torrents/torrent123\",\n \"info\": {\n \"file tree\": {\n \"tentacoli-01-Small Town Pleasures.mp3\": {\n \"\": {\n \"length\": 3177585,\n \"pieces root\": \"e755700c5bcea4905a1a3f900351d8a5564098bc081e6754e379f805728d190a\"\n }\n },\n \"tentacoli-02-She'll Never Come Back.mp3\": {\n \"\": {\n \"length\": 2778997,\n \"pieces root\": \"d1c3d80bf13fd42b3e8582a15a21172d23b80247f3b6ca85ffa97f018db4dfcb\"\n }\n },\n \"tentacoli-03-My Son's Friend Is A Champion Pisser.mp3\": {\n \"\": {\n \"length\": 2333412,\n \"pieces root\": \"3b458ec67b680d056b9f48d2920242bd812249cebd1939880340e7d2e136fe0f\"\n }\n },\n \"tentacoli-04-Summer And Winter.mp3\": {\n \"\": {\n \"length\": 2562622,\n \"pieces root\": \"3ff20c6f038c048f27e428499498e1d19488f8f934df6632d5d4d827ccf3f3f2\"\n }\n },\n \"tentacoli-05-San Diego, Yellow Cab.mp3\": {\n \"\": {\n \"length\": 2796486,\n \"pieces root\": \"7ad85e1400e68597e006040d4d9373d2a05d9b2ae13f14dd9939317d33ea2c8b\"\n }\n },\n \"tentacoli-06-Happiness Is Having Two Killer Whales As Friends.mp3\": {\n \"\": {\n \"length\": 3863849,\n \"pieces root\": \"d0063c2d1f1c5e3b5e163383d5f5a63ac983be59708e0d695870442cebbe37d9\"\n }\n },\n \"tentacoli-07-Too Risky A Day For A Regatta.mp3\": {\n \"\": {\n \"length\": 3696387,\n \"pieces root\": \"4767dc7f1b8d2f6aaae276034cb2e2c6c656af56683e148f0e54c15382273245\"\n }\n },\n \"tentacoli-08-Sorry, I Have To Go.mp3\": {\n \"\": {\n \"length\": 2583746,\n \"pieces root\": \"661b7dc0bb901d026370be0b73e87fb6a95479dc85bcdefe61711d8fc5b236f3\"\n }\n },\n \"tentacoli-09-Scotch For Two.mp3\": {\n \"\": {\n \"length\": 1924024,\n \"pieces root\": \"6bdfbd145a64cabfc9b0971f87ae69c93bd1847b0c48351ed971c1fa4e9db97f\"\n }\n },\n \"tentacoli-10-The Killer Whales' Games.mp3\": {\n \"\": {\n \"length\": 2463949,\n \"pieces root\": \"e5273e3a62dee1a16328471a471f3a361eee0de07de68a11b6695c8483a4eb88\"\n }\n },\n \"tentacoli-11-The Capture Of The Giant Octopus.mp3\": {\n \"\": {\n \"length\": 1783901,\n \"pieces root\": \"91b3f6f33d7c2cc4752c3b6e79bafe7ec2d3e03c31ae3790a5c132d1a9b93f84\"\n }\n },\n \"tentacoli-12-Two Old Kids.mp3\": {\n \"\": {\n \"length\": 2965602,\n \"pieces root\": \"0b09f31a4206b8c42530dc7fb489ea52f4db6992b1de8c9e56dd1bf711d64af4\"\n }\n },\n \"tentacoli-13-Tentacles.mp3\": {\n \"\": {\n \"length\": 4319196,\n \"pieces root\": \"26c6744b0b3245ed23691732c22f95a9bf074f32a49ba5c9feece809347e889e\"\n }\n },\n \"tentacoli-14-My Son's Friend Is A Champion Pisser (Versione 2).mp3\": {\n \"\": {\n \"length\": 1555839,\n \"pieces root\": \"736490ac9a57251d5feb02bb8e17d0f29e761993bd604ebece2f66e8cfc24a76\"\n }\n },\n \"tentacoli-15-Tentacles (Versione 2).mp3\": {\n \"\": {\n \"length\": 5261174,\n \"pieces root\": \"4c94d4d2d20631afa6fa4d4287af4cf5c262c00f834c027c9df3f711adedb2db\"\n }\n },\n \"tentacoli-16-San Diego, Yellow Cab (Versione 2).mp3\": {\n \"\": {\n \"length\": 2877921,\n \"pieces root\": \"b9f50e8baac6b38dc2f84e93387b8c5e21c334f54f8772cd5dc819e88c671c6b\"\n }\n },\n \"tentacoli-17-My Son's Friend Is A Champion Pisser (Versione 3).mp3\": {\n \"\": {\n \"length\": 1079679,\n \"pieces root\": \"a2781d81c2dc33dac179bce14390485e47f2129ba98ed4efbd4690daf7229d95\"\n }\n },\n \"tentacoli-18-Too Risky A Day For A Regatta (Versione 2).mp3\": {\n \"\": {\n \"length\": 874993,\n \"pieces root\": \"c67de66bc0c2c41fce411d351d82dff8afe809e5206df6b14d536d4ff4c00dbe\"\n }\n },\n \"tentacoli-19-Tentacles (Versione 3).mp3\": {\n \"\": {\n \"length\": 3951350,\n \"pieces root\": \"b6145263d70c69c630d59dfa07856663b33e5621a62a33d7cd706c3aafdfe2ed\"\n }\n },\n \"tentacoli-20-Sails.mp3\": {\n \"\": {\n \"length\": 4358356,\n \"pieces root\": \"72436d6a0686a9a83d782445224036c71dde61e1824ef5ea230462ad95daa11d\"\n }\n },\n \"tentacoli-21-Sails (Versione 2).mp3\": {\n \"\": {\n \"length\": 2870766,\n \"pieces root\": \"b27df2732173f94fb73fece8d7ca2864d2c027d5186f078d30c01e4310d700e0\"\n }\n }\n },\n \"meta version\": 2,\n \"name\": \"tentacles-tentacoli-1977-ost-soundtrack\",\n \"piece length\": 524288,\n \"similar\": []\n },\n \"piece layers\": {\n \"0b09f31a4206b8c42530dc7fb489ea52f4db6992b1de8c9e56dd1bf711d64af4\": \"VPZdDK988MyI5L2JUEvc3RNizYDkWZBhaxZGOqqjJO6N5c5QVL/+NUNQvc6AyZtNFfD2A/APiNK/AVNMjhT2WjgTQeqMBtmnoLKBs3+F+e94T7NQk5uygqd89lOk+eFSW3zIxeoCgjnxq3QlX3i8ABOK3RDVvzFPnegQvpgYoBH+3TlsWxinQ+JtwGFyTMAFwe52fIRscF5UgcqdlgRyrJu6EzKob+25Ngq/Slv0Y/Ij1mSHLRcx6BRy960JjT8J\",\n \"26c6744b0b3245ed23691732c22f95a9bf074f32a49ba5c9feece809347e889e\": \"nIooZfqJg9f3Wa+Kko4aSSlqdLkymMqh+qvpz640fLgJHa+hFhvvQkGCETr/xzUT2YIBwtWr+/8jGUCpE7dWHoSL8ihQXdoNoOXHYcwy/fPygSMJl+r5IgatqWJOAKSX8zyncD6096iCsazYOgp1ya2CkdxNJPpCAyXVXCl7AoPV/ULP7gKn0G3WcqAE+eXPEmLkdzEBI7KWNXsFfhyl5uPDY0RgzfirDwOeC7e34jPxBqxHcfsLQJ1ORDpnhOO1F/Hh3L4hFfiiFifXwqu/tjs7yOUkmKwhpzvr6O5wvA26p/Igw5ztlqj6wlVGVN/ay5anFZB6wmLCmGNt3M7+W5afOyAk1tvRUpNoOMTAabxT0YMPUDiuP6bJHZPsOnG4\",\n \"3b458ec67b680d056b9f48d2920242bd812249cebd1939880340e7d2e136fe0f\": \"hmopzDsPaqQY8kB0fSAE9korQ/EYlhMNNKYh+Stip0nQeIednxVIigWz7SdHjEAxtrISO8Q7oPicQBePe6P5yH70mQLp20KAj82QuAtpEUXP582Ix5A/QTAlv/ZOCCV9bUeY63aSacDLrVzjyx0cu3O0I18d5lMa7GL8TszAeziVxSJDStgC/mSgsEw2lyjyzchw2XHI39o7x+1GlXHhZA==\",\n \"3ff20c6f038c048f27e428499498e1d19488f8f934df6632d5d4d827ccf3f3f2\": \"V8LPZiW6dYEhkVpy50NA8NQobcjCG7BiwqPR0TzNXe+rfk+pF/p7QCzF+tpKReBi5EPurqWGjO4+dbJ73YOO53yCOoLILCo9iji8YyvkB6RQ9nM4Xn71hPiU1+oJwAQb3cgM6dxz5+ida8bDGLpB7CroBXzQniNAbNUungzhJy2T6O5BwEHBoLJmpTrkcLurMqLmHRmGJN2SMqfcMDhg3g==\",\n \"4767dc7f1b8d2f6aaae276034cb2e2c6c656af56683e148f0e54c15382273245\": \"gAhlHM85lyfo/VNx1txeTrZogY4hwv1y2GQ9PrXIA6zqco3cpn6IIttXkb6GgIkz4PiVmEmC4vlKt/rV+mCr6/hj9o7tmCjolGiDw471NFblaxYcYKu+XfM/coV8GJGtAV+bWjotIAccxZUq+JpcMfVD33C9H0PKjX14NLJwAG02ZRMcu5gU1FEjypDUXi/uGmeb9LquiwDv7c9ZJ+tTqH7/K+w8w45GI3gt8B+ZRa4pPUlDAsS9o3efdLbpwNrwvbIgc/PNpmSwoSkDHtik6bclpq+IKm0oB0eK12ic6/NdOTH7eMZ0+FZ5rtn9ThJ028cHkSAeqHs8v1e+UOjx4w==\",\n \"4c94d4d2d20631afa6fa4d4287af4cf5c262c00f834c027c9df3f711adedb2db\": \"AFW7D3V3l7kOP+FoujXugKWb4HH2pXMOc53bQaUj5m3r7xXsXQSTox1chHUlK8ps+O1qxckSydl7e9pPXWN+j84nVqScDN6uF+LJp58SxqOmi/g7nBBJugwxPaf5HrEOAXynbi+CnHFBQPYnZ7/AlSFhpII4j9/Nnv9WT079YgFCc162lViJR860+Rbf3OaATtVAI5oGFJUAVCV636zAsTXoTMj0lnJne+Hb/KgxlROiWKLBDAL1jQ/0Cz3n3D/Vyw4eAQ0AaP0REXemD/wBFDyDE+5ixFS9+cI1uam4bTqGN4UPily/WRC49VpyAwyT6rpMFjTXc7xGTkMEZpNIgbENH8rZxBWFDoNSzJVRL/XjdOZaDFKe5ALRxeQAzbYh7tKeqdi4OXE0p4jzBd8pC+hNM3yDu8LgHDE1qjOM4egkt9gBtsmnSi2Urif1/Q8maaiy3sEHoph4VdA9D8N8nw==\",\n \"661b7dc0bb901d026370be0b73e87fb6a95479dc85bcdefe61711d8fc5b236f3\": \"aZRzmN/dmNjg+FsfKWMZl0l2siifCKnrMYFM2PA5lOOB8DclDu0sXUM55se9HRMVjOcnfNXxY3+mRIrtTdaT8sDbpzHXp9iGqsYZBGEAj07ZaeJM3jHvOky1o4x2Qro5JHtefe2GFDgq2lJS0ha+JUsWG9a6MdH3c3faaj/X4IVoJCDtF/V8WpvVY8eMmVoEA5L9Zfpc9eiTlCJNP7HwIw==\",\n \"6bdfbd145a64cabfc9b0971f87ae69c93bd1847b0c48351ed971c1fa4e9db97f\": \"sysPGOfDZ8wAr39CdfFK3MzWxiPexd7SkLqWvIySoYD/Gr+n3xJTMyxIZR5LwAjaQwAlCqRpgDx14e0kyWWKQBUPlj3EgcMR2/1vCW6mBHFTqfO2Y/IECE/iSUaFJ3i+zWEQcAklqVfqBloKbLGtfHMAVIRgwY8yoAKB23+gmFk=\",\n \"72436d6a0686a9a83d782445224036c71dde61e1824ef5ea230462ad95daa11d\": \"QiFoWXevBNl/jPCnBtGEtE4VfW9k31yMrX8MrTtJs+2DpNXJXQIWYPGaXA5S1iOnhQ1wBXlKt+m7M6TChTE/5/q2qPpe4hp5iVl28bkvb2pYgVZSgYiqGqistUewLisrhTygHutf8Rm2ChDp4cfJAJ0hRc2hHQpMo9syRwpVUYI2HpVjsS6t6Ymokojaxfdxk+88mJ1bJZcNNoFmK5EaqsQVRvjTenxaklYKPZROeDeiUb64FGCTvv8hF0T1/cNFCOV3kdUjgM4mrnw+gDfJIm5JdID5bsC4DVF7WrOWaAH251lWksPzDyIFcSxV0C7l+FihtXM4U/BhsxlnnfWEVfWZBP7tFb4vMU3WsMRCCbbXCsKpuNa/NYCwro+h1MO/\",\n \"736490ac9a57251d5feb02bb8e17d0f29e761993bd604ebece2f66e8cfc24a76\": \"VwRUXEzJHC3pz7uEnEowpwFHA/6Q4WYdUmMEoA1Fvog09IALuR6E3bAuo6FP9pTrn2bvxPvJSKHVhGyWwbGaJn5arMAK4ChGGuZyoAQwOOdQPwx/A90v92qo5SuqhxFF\",\n \"7ad85e1400e68597e006040d4d9373d2a05d9b2ae13f14dd9939317d33ea2c8b\": \"SCiEVcZOjE1GM0aMNWiBTiS7LMV2iIAVX4puj77Dhz+dsqG/hthLs+d6GD3oB29I0wCBYIPUCSDJmcj0ppAvABDDhc0H1bIpQzhqHQmwtK80T122VAQ2MPKCU9vg+k8wkJSzgWKib8AQJEpdpBpEiyO9DPCzSfzLtqPucgTUNvlatKYAh5c8+o0Nf1ewEkZumL58vocsENGianJDe+kSzUJMwJKxT6BvFlE9ujsZ7fSWDV95sVxqOQMFCVwxuNOD\",\n \"91b3f6f33d7c2cc4752c3b6e79bafe7ec2d3e03c31ae3790a5c132d1a9b93f84\": \"vj2xl+bqofTamS/TSAcwBbsf/E5jBX9C6lgZdswP+/7WNv4Vp15ny5mxi14YqCFG+M1+5NOfUzJw6RiwMLWY9R82q7yrrW4ccnBJMcvhP7f39D6BKMx4mcd/ATr0aEV5KbzkuW6io+4uahSt53fk53UMkGAwS1kGivE17ueD1ng=\",\n \"a2781d81c2dc33dac179bce14390485e47f2129ba98ed4efbd4690daf7229d95\": \"TpFZZj49ysDTd5yoplRz0DfoipqsWnG4ZNeNbZA+8M9rTS1ItmqZnol7sDqbMXZ+qrsXiYEr2pWCeaks/cqPI+1ae1ecX8rtsWyA3ej5aGRNt9zUmpYhDazGrqfok2v2\",\n \"b27df2732173f94fb73fece8d7ca2864d2c027d5186f078d30c01e4310d700e0\": \"jnrUt1kqrGZ1gPKtCYyDocTIoOxB0RtsWA3xEocJiyxaUaWv8jWumQAjW6ZMboJ10JODGebBV7HQssjlElEVdmR6HnePf6Ov+Fu/ZfplQHILfLi63PQYO3Dc8ohdwEz3o+FgJ+JOhQwbynrSItQOT+azQLJ8o41yWOGY72SUdhtNEjhytqWwGQIHFKMJfQeVlvLpVCryU6p84sgxIiYRJ8cUZbHPhz4Y0m3xPbR1tJMaIlqcfjOw0I9sHYCA4suZ\",\n \"b6145263d70c69c630d59dfa07856663b33e5621a62a33d7cd706c3aafdfe2ed\": \"GQLFRpTn3AVBJS/ojGtr9lhfSI/8ewYR0xLlkqg3Cnqx2AVlwr9RwBjKMajHSgcmVvpQpoZ0AejThvZz2JygF5kkjVy1Pe7zJ65GfmIMU0CCKkiAyCWuQt1PdDTd2xWNReKv7MH7rVRse7H6YrIAZSdEnziGr6eUVt9+fxULQm62g4rihawxEW6dZvMh8QL9D+PUFVjrgRBUKvYWqRXgRY0vObRxqCQj1AJDSwvUdMv33b7o95rNOsVEdBu+n+Lt3JA7CxwAXsQrgZt9AmGXQTvyu+uONt+2ihaqtspuGAkBf6/D8RN/sybcWtWCI0lgSyqu2g8cYBEMaxnjgKGSaw==\",\n \"b9f50e8baac6b38dc2f84e93387b8c5e21c334f54f8772cd5dc819e88c671c6b\": \"snFE/oMWtUu01QzaJnhsMG+D5D97pbKW287lAAMvMSwp0FK1aOBcV19dvQte9qvXmws494090AGQ3xxAOntIFQjzJlYhbp8JQC+2r32oFf7lyaowwreV535RdH6/TNcd39O9UI0dAndQ9uDWP9mRpBFfLksTvYFktKnlwqqCiYRAGbdsO5FC1qlJxi/X1CZHG9gjk1FxdhH0WLUehsdQwwxuoXxdQ12HLuowXNBr1JeAAWNcLVAqSezuUkulc/Or\",\n \"c67de66bc0c2c41fce411d351d82dff8afe809e5206df6b14d536d4ff4c00dbe\": \"GaAYwCiYeDqYOop50ZEV4TcybE87ULDDE3Nv1ZG4geIF6cRyFsjN+IAOqiXe6wjRkdReNo3NLKskpp61Ca2rBw==\",\n \"d0063c2d1f1c5e3b5e163383d5f5a63ac983be59708e0d695870442cebbe37d9\": \"fi5NJSOcrOVs57ny3dnPOhs/bIbTzJOYgLEGbq+cPwOkBPtHyY+BbAoVmFzgGyjVo+htFC81LKvLtgOMRk/TcrS8wdrRgB4NvawjS5Mk2+0LNGitxFrWZym3KhKNa4oTM6d+UE0wrGWAsOcm/fTxxGRETf4OR+gHpjz/4ED95uAboFk9h317NMudz9etX0IjX25HrBDVRH/cBJ4Kw2G0iwIVXFWQOfyBGIvQ3OUWLVd6pq7cKnYEv9V98EfpVuJquGsoDb08lJZdK72qzsxlIAXMSqhYjvEsXZD5w8wiqUeMs5XuN/DnzgDl8OdPGAUnaf8f5T0NEPvQjNMN4z+aiw==\",\n \"d1c3d80bf13fd42b3e8582a15a21172d23b80247f3b6ca85ffa97f018db4dfcb\": \"nZ0lMxZNxcUhIGdtCyi9FXVtv9WF43rLL6S5nCcQD5ufOCkq1YNu0PqtK7c6g89TZlysL0dZ/xqWIeIvsn927cVF+ugd6jvoFFcpmr33scLhCbT2PqN6A1oSYA75CgBrzlJpDzZiV5uwZzBWnlu2/menX26DPBbV67Qz2Fbn71+XwW1aBn9DR1i6MilHdyVMLpZn0C1gpgE1TG41//OZWg2N2QdACzAYK3UMoauKVDnDr4x3+cgPj8cuvjgMV3xk\",\n \"e5273e3a62dee1a16328471a471f3a361eee0de07de68a11b6695c8483a4eb88\": \"+mHCqf0cQomJkl6nWynCwp6sCWdbMlZvd78BdV3SkFJcwwolpXPAf2kZZYeUXs8X6HL3xOzFvgJLiCyS2gQ+TrG4iZThREyKwgw5eZVWv6AvxzFU5UntjCP/8R4uVWjaRNf4fupVWANkqYFZmcL0mxGk+b2y0ZH8e6kECzHCT6ZQDXvQMtUtFcg6UfuWNNmf3ha+A/M65Hs/m0q4ccp4Ww==\",\n \"e755700c5bcea4905a1a3f900351d8a5564098bc081e6754e379f805728d190a\": \"+aMdZEEaKoLp6RGQeKcp6QbjVdiqEsRghVOLxndG1+abKpoajctleyvXwAk5IszYUYyQoLt6Aj62v4256vygQ/mITuOkN3OQ6l3EaYlUwi79fu5jZvhpzgTLIXWglogaCZEft8aQ12G8m8DO6+GhLI/KqLLGGOayeB4vIq9aMjcnWIEYq5o/vrtTpVGnJFKNgAP9FbiLQka3RvJiEHWw2bMgpKspS8ai+fLNR5NqHEfzQRlxYVcK6ieySks/AcQ4m9X0olJYa0Hh6Cj4Md7pHaYvkMioFshhs+z4HFwnP0Y=\"\n },\n \"url-list\": [],\n \"infohash_v2\": \"d655cc657b0b56975c2596ef1c493055565daccaf2ae29cafdea22bd7cc80e6a\",\n \"type\": \"Torrent\"\n}\n"},{"location":"fep/d8c8/#hybrid","title":"Hybrid","text":"Expand/collapse Hybrid example {\n \"announce\": \"udp://tracker.example.com:6969\",\n \"announce-list\": [\n [\n \"https://example.com/announce.php\"\n ]\n ],\n \"created by\": \"Example Torrent Creator\",\n \"creation date\": 1762216376,\n \"id\": \"https://example.com/torrents/torrent123\",\n \"info\": {\n \"file tree\": {\n \"tentacoli-01-Small Town Pleasures.mp3\": {\n \"\": {\n \"length\": 3177585,\n \"pieces root\": \"e755700c5bcea4905a1a3f900351d8a5564098bc081e6754e379f805728d190a\"\n }\n },\n \"tentacoli-02-She'll Never Come Back.mp3\": {\n \"\": {\n \"length\": 2778997,\n \"pieces root\": \"d1c3d80bf13fd42b3e8582a15a21172d23b80247f3b6ca85ffa97f018db4dfcb\"\n }\n },\n \"tentacoli-03-My Son's Friend Is A Champion Pisser.mp3\": {\n \"\": {\n \"length\": 2333412,\n \"pieces root\": \"3b458ec67b680d056b9f48d2920242bd812249cebd1939880340e7d2e136fe0f\"\n }\n },\n \"tentacoli-04-Summer And Winter.mp3\": {\n \"\": {\n \"length\": 2562622,\n \"pieces root\": \"3ff20c6f038c048f27e428499498e1d19488f8f934df6632d5d4d827ccf3f3f2\"\n }\n },\n \"tentacoli-05-San Diego, Yellow Cab.mp3\": {\n \"\": {\n \"length\": 2796486,\n \"pieces root\": \"7ad85e1400e68597e006040d4d9373d2a05d9b2ae13f14dd9939317d33ea2c8b\"\n }\n },\n \"tentacoli-06-Happiness Is Having Two Killer Whales As Friends.mp3\": {\n \"\": {\n \"length\": 3863849,\n \"pieces root\": \"d0063c2d1f1c5e3b5e163383d5f5a63ac983be59708e0d695870442cebbe37d9\"\n }\n },\n \"tentacoli-07-Too Risky A Day For A Regatta.mp3\": {\n \"\": {\n \"length\": 3696387,\n \"pieces root\": \"4767dc7f1b8d2f6aaae276034cb2e2c6c656af56683e148f0e54c15382273245\"\n }\n },\n \"tentacoli-08-Sorry, I Have To Go.mp3\": {\n \"\": {\n \"length\": 2583746,\n \"pieces root\": \"661b7dc0bb901d026370be0b73e87fb6a95479dc85bcdefe61711d8fc5b236f3\"\n }\n },\n \"tentacoli-09-Scotch For Two.mp3\": {\n \"\": {\n \"length\": 1924024,\n \"pieces root\": \"6bdfbd145a64cabfc9b0971f87ae69c93bd1847b0c48351ed971c1fa4e9db97f\"\n }\n },\n \"tentacoli-10-The Killer Whales' Games.mp3\": {\n \"\": {\n \"length\": 2463949,\n \"pieces root\": \"e5273e3a62dee1a16328471a471f3a361eee0de07de68a11b6695c8483a4eb88\"\n }\n },\n \"tentacoli-11-The Capture Of The Giant Octopus.mp3\": {\n \"\": {\n \"length\": 1783901,\n \"pieces root\": \"91b3f6f33d7c2cc4752c3b6e79bafe7ec2d3e03c31ae3790a5c132d1a9b93f84\"\n }\n },\n \"tentacoli-12-Two Old Kids.mp3\": {\n \"\": {\n \"length\": 2965602,\n \"pieces root\": \"0b09f31a4206b8c42530dc7fb489ea52f4db6992b1de8c9e56dd1bf711d64af4\"\n }\n },\n \"tentacoli-13-Tentacles.mp3\": {\n \"\": {\n \"length\": 4319196,\n \"pieces root\": \"26c6744b0b3245ed23691732c22f95a9bf074f32a49ba5c9feece809347e889e\"\n }\n },\n \"tentacoli-14-My Son's Friend Is A Champion Pisser (Versione 2).mp3\": {\n \"\": {\n \"length\": 1555839,\n \"pieces root\": \"736490ac9a57251d5feb02bb8e17d0f29e761993bd604ebece2f66e8cfc24a76\"\n }\n },\n \"tentacoli-15-Tentacles (Versione 2).mp3\": {\n \"\": {\n \"length\": 5261174,\n \"pieces root\": \"4c94d4d2d20631afa6fa4d4287af4cf5c262c00f834c027c9df3f711adedb2db\"\n }\n },\n \"tentacoli-16-San Diego, Yellow Cab (Versione 2).mp3\": {\n \"\": {\n \"length\": 2877921,\n \"pieces root\": \"b9f50e8baac6b38dc2f84e93387b8c5e21c334f54f8772cd5dc819e88c671c6b\"\n }\n },\n \"tentacoli-17-My Son's Friend Is A Champion Pisser (Versione 3).mp3\": {\n \"\": {\n \"length\": 1079679,\n \"pieces root\": \"a2781d81c2dc33dac179bce14390485e47f2129ba98ed4efbd4690daf7229d95\"\n }\n },\n \"tentacoli-18-Too Risky A Day For A Regatta (Versione 2).mp3\": {\n \"\": {\n \"length\": 874993,\n \"pieces root\": \"c67de66bc0c2c41fce411d351d82dff8afe809e5206df6b14d536d4ff4c00dbe\"\n }\n },\n \"tentacoli-19-Tentacles (Versione 3).mp3\": {\n \"\": {\n \"length\": 3951350,\n \"pieces root\": \"b6145263d70c69c630d59dfa07856663b33e5621a62a33d7cd706c3aafdfe2ed\"\n }\n },\n \"tentacoli-20-Sails.mp3\": {\n \"\": {\n \"length\": 4358356,\n \"pieces root\": \"72436d6a0686a9a83d782445224036c71dde61e1824ef5ea230462ad95daa11d\"\n }\n },\n \"tentacoli-21-Sails (Versione 2).mp3\": {\n \"\": {\n \"length\": 2870766,\n \"pieces root\": \"b27df2732173f94fb73fece8d7ca2864d2c027d5186f078d30c01e4310d700e0\"\n }\n }\n },\n \"files\": [\n {\n \"length\": 3177585,\n \"path\": [\n \"tentacoli-01-Small Town Pleasures.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 230287,\n \"path\": [\n \".pad\",\n \"230287\"\n ]\n },\n {\n \"length\": 2778997,\n \"path\": [\n \"tentacoli-02-She'll Never Come Back.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 104587,\n \"path\": [\n \".pad\",\n \"104587\"\n ]\n },\n {\n \"length\": 2333412,\n \"path\": [\n \"tentacoli-03-My Son's Friend Is A Champion Pisser.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 25884,\n \"path\": [\n \".pad\",\n \"25884\"\n ]\n },\n {\n \"length\": 2562622,\n \"path\": [\n \"tentacoli-04-Summer And Winter.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 58818,\n \"path\": [\n \".pad\",\n \"58818\"\n ]\n },\n {\n \"length\": 2796486,\n \"path\": [\n \"tentacoli-05-San Diego, Yellow Cab.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 87098,\n \"path\": [\n \".pad\",\n \"87098\"\n ]\n },\n {\n \"length\": 3863849,\n \"path\": [\n \"tentacoli-06-Happiness Is Having Two Killer Whales As Friends.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 68311,\n \"path\": [\n \".pad\",\n \"68311\"\n ]\n },\n {\n \"length\": 3696387,\n \"path\": [\n \"tentacoli-07-Too Risky A Day For A Regatta.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 235773,\n \"path\": [\n \".pad\",\n \"235773\"\n ]\n },\n {\n \"length\": 2583746,\n \"path\": [\n \"tentacoli-08-Sorry, I Have To Go.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 37694,\n \"path\": [\n \".pad\",\n \"37694\"\n ]\n },\n {\n \"length\": 1924024,\n \"path\": [\n \"tentacoli-09-Scotch For Two.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 173128,\n \"path\": [\n \".pad\",\n \"173128\"\n ]\n },\n {\n \"length\": 2463949,\n \"path\": [\n \"tentacoli-10-The Killer Whales' Games.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 157491,\n \"path\": [\n \".pad\",\n \"157491\"\n ]\n },\n {\n \"length\": 1783901,\n \"path\": [\n \"tentacoli-11-The Capture Of The Giant Octopus.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 51107,\n \"path\": [\n \".pad\",\n \"51107\"\n ]\n },\n {\n \"length\": 2965602,\n \"path\": [\n \"tentacoli-12-Two Old Kids.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 180126,\n \"path\": [\n \".pad\",\n \"180126\"\n ]\n },\n {\n \"length\": 4319196,\n \"path\": [\n \"tentacoli-13-Tentacles.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 137252,\n \"path\": [\n \".pad\",\n \"137252\"\n ]\n },\n {\n \"length\": 1555839,\n \"path\": [\n \"tentacoli-14-My Son's Friend Is A Champion Pisser (Versione 2).mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 17025,\n \"path\": [\n \".pad\",\n \"17025\"\n ]\n },\n {\n \"length\": 5261174,\n \"path\": [\n \"tentacoli-15-Tentacles (Versione 2).mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 243850,\n \"path\": [\n \".pad\",\n \"243850\"\n ]\n },\n {\n \"length\": 2877921,\n \"path\": [\n \"tentacoli-16-San Diego, Yellow Cab (Versione 2).mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 5663,\n \"path\": [\n \".pad\",\n \"5663\"\n ]\n },\n {\n \"length\": 1079679,\n \"path\": [\n \"tentacoli-17-My Son's Friend Is A Champion Pisser (Versione 3).mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 231041,\n \"path\": [\n \".pad\",\n \"231041\"\n ]\n },\n {\n \"length\": 874993,\n \"path\": [\n \"tentacoli-18-Too Risky A Day For A Regatta (Versione 2).mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 173583,\n \"path\": [\n \".pad\",\n \"173583\"\n ]\n },\n {\n \"length\": 3951350,\n \"path\": [\n \"tentacoli-19-Tentacles (Versione 3).mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 242954,\n \"path\": [\n \".pad\",\n \"242954\"\n ]\n },\n {\n \"length\": 4358356,\n \"path\": [\n \"tentacoli-20-Sails.mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 98092,\n \"path\": [\n \".pad\",\n \"98092\"\n ]\n },\n {\n \"length\": 2870766,\n \"path\": [\n \"tentacoli-21-Sails (Versione 2).mp3\"\n ]\n },\n {\n \"attr\": \"p\",\n \"length\": 12818,\n \"path\": [\n \".pad\",\n \"12818\"\n ]\n }\n ],\n \"meta version\": 2,\n \"name\": \"tentacles-tentacoli-1977-ost-soundtrack\",\n \"piece length\": 262144,\n \"pieces\": \"DplxVW7qZwpqi2b4dkeZeSfQxe00lI+NIAPgqutlOvUJEOiin3DotZcSM6OWGcgmsU7Zi4oQfSLhO4qXdxP3td3x5UL8eHeIpnaJE3Cme9gqaBRdEIfFCVgZanXKMRnzPQ/aoscXWPuiMX0t1Ytjqvag+4yiwxKeeK383m6l/sDQxKceDIf2I/+BiHa/q8LHVvYuAMSGLTXmHzU+bUC5V6cEFfcp/Sd53DeP5gwSPJvNyXX+parYLrqrh4BzHhHRwBluiHyecCUuLcEYmY17+5oYtzyF9og3UtK4MgXUt9MftFAColm6XgHiGTHgy5gn9p3MNL4k2fns1tUAIgRelAMnVF17WwtHj7DQqW3uq7g+9V41bsRYvyNaACsDqxHgB/SXtAqiKGPowh6UdwID9H/BmMrK3kPSuev/gniVE++W7XtZVWecdptUWZQlRMWjaQIXEEUTJlweZ1o4DBVMnyQZU2ZFkTySWCZkpjyUq7IBZ0tLnX4Q1bWW8JOH9wdw9I3TlBRIn05YTO7DGN2+d6xzjjbwBpl2TVkLo/EvyAckO52woS6CLeP7DycsqFmpJySSpbJ4m19RgeC7VZQFFed54Hv/z7mQURMyoPoHEM2anO7UZRun+aHKw2+1ugOKWX5JEDwwEwbFTDhiToHaKnlC0OUJhxTBB9n8BtICTtdDHHLOdg9Nh/E9qZBA7jZHAu9XULXkTyuVABWvA71ieEagDlzRnHjfFmRrnFg6MsQGKVc9QTmw1OZ6eWSqosJVzuIIPsBgwgkPrPXkS0CT3l/DwtkWBRg8TsGz3qOKH5vBWlWiDuNkx5QpfNmpECJRm6XX0n83NXXhXFbAudU7iLvj83LeKRBFNoUJG1asXHGFmd65y6GiX2oAgbOk/3YMHfb60fVEWV3QGOIFwgfcfDFBP8luZiuw1ld95G2W18TrJOSWaaYZnt115wT5x1h3xIi6ide6PCn+AXNqVTRAR0TmQhflW8/YP7EN0RXXTVnBPIUtIWH9xNarDAUHoAKNVZOkAHJSwLv691FyFmD+gS8OBO0OaH7lgC7uFlpSC2P64YYbixnjq2QinqLaPKtkDv4cgCRJQ4ns98qEz9Bewg7f/KbDd8nXJ7Bws0ZSLtPFiwq1+jvlMsEIIOCGldL9TCX39TsRI3uPY9SjW55XA/FVVVkQpvZMn2so+kqhq49UTJCmmxNpCh6lUBSu11x94HyYzTVu9KTaO9s2RGC6RtNpO0hMSjmEOrRQyvA9IUFHAQqqnzjtTQ1BgHOakcUb1k2ziyadfV4DJr1uecQ8GXTTCcQPeQzkUc+HHeezsQs5rwDkQ94j6zqfjgI2HGTlGVdh2QOMJOaRgTzFVIlWZlO/jRo5ACovJunOzES+C2mqhE68DjW81Z91ywbDha/4yE94W1LOlrsQnz6dlE5Ed1g0vedkogdwdyWfRBgLOcAgoQj8iWM6YvDr4orlp/zPtYEt+QLNJY6bGLj1rPspHKs3pKOeyEi7vpNDpY7M+3Mgaqb+NqWj86n9CJGAnXkEOXuG58v5PgJsyBmeQcjLaeYMVhQ7HARFu4ZHy9hi9QnMMztWiiW0M4dSlTJsi5Kcrrfl/yDLIoJ8ZNwcuCJbnKEUT50UDYHcFLscR7ZjJmGBGblGGxs86Q4U4HQguOZ8eBllBCb1gjCDegfFQq+qaMQhHH0tsrbVzpJywCnProjvOJyAyISgMKuquMIUmLUIuHih58qhZqmgsXxdJE2t/eU/asAZmVTBF6fGZhQEAfGg3bdqKhakY0O4uZZz9b405sRaaUaCK88waYQrS038AIxgroCNYzdtR4scDBwqTvVffEqZvJ2hIuBI34DtKkvuwLtWL/S27tlHBMOwmEbCLWMQfrUUhroC0ONNlcElA5g54W7edDZafI6CW34ImyEOIsljk27IRrB+CKIuEqCgLh+HxAnpUp2nNPRWTQHEMvkfO46i6dMBvHPH+Pilt04UfuTjl9PSHO7Ry6XDkssB6ANASH6UFW2IEDiHnRfL3VTN59DTtg4e5uUBxlnIw1b6T8XUDzRjGZ1tYLSCbA+7U/UT9z/Ae/YVuX5YgoTKTP3WwMmQ6LtFLK1PnXM1ICuzJXLijBmkqu28JN9l+HDxXQu+oZOGH403RN0U0Bo9YQWXpgljLFj9jifz6rFOGZLL/gdFHomVRUY1+w1/BcB+MLFKxOzrovhBxV3jzL1dRNiYSQHFFRkt2NtCWlGhga5s0y+xkBM37GBJ8ykYEieAV3LdUlWMIlaGfLvO+938thoV7MK2JC0dNhvpL4GwIBymhGsuUaETQC0vjB1BT4aTo8SRqHK6B86M8kmNNmk8+eqvE1KNYYLofAkf/FGtPwaRQ9dAPxF25qrF1JUw88H8HDZjgmxxYXlU15gn3CM19TNY3c6elH50nuPvBHKiXL8XRVGPRbVYS3lQhJwUOOYqf6kbXasuLpshTHx1IVuZWCzESNHp5To8JvY1WymiYBr2Wx9dh9lpV1qOx25p6Zm/t5oM0VJ/xFK/Q5+bWQzL0rE4Z23WsFwFXsJj3tHE44CRkVfWN3saz/QnkA90QVWwn/ACyK2zm9yaEGR/FXr/LYj3Dzx1U+oYK2I+s1CZfdO3D7VaH6pV5hqQPmW+LSQHUx4Lq0W+FGIT43kumRaxrtuj9fS9n428czT8eb5ok4UZS03GiYCSx9+TL8Hu3tyhhPBKrGmXbLBDP5ZwtGUBir1s9hav0j2OgiIggPMBWKrJSp1nfZFpfN4zG5YL4FAF+ZcJSaqmhoFFJykiQypaMh6eGRoTM0l6Q3ca+SR9cK/QwvcoARlP/gEvRp0wFeb8THOUWyEhYL+WuaMC9Glj5vW//HekRvn6HzDoYepcTFZRnPPupwHPF8VLa9TxUim3PcqZbufhfh4mGxzAVgLCDLxLshINscSMDI0DVkzOJFGEo/0r/8GnuE61CH5FMdaIwbJWC0IamDbCGnoPwSa2AOOKsgU3VkbhgariWoyM5+3Cg5+QJprRL8fhaqiiLg5E1oK4sMgL8C0xbZMOiAj8fIihjxQjwIWx5c+kjgB9rdp4hTUc9GV8uWCyp8fDUNsrIKP34uFqjpwabImsK93OQT9jMYiCQ7QmzCCF/MBoQbH86pEPkeBbtij3u6VOIRgstmz7kdxgKQ1LDSZSvSRibyguLJJaJzZWGYQgwETr95R50Qeb01xLUzar6MV+gI6m3Wv2s9h9nqzvREvtSD4m0pVwGq0ej+gxR/lJquvLfwaidL21U1S115gXko5GXknnMHF9reNTxrmJMRabsugb4U3Q0TRNhofh91BwbOr0nVma2FI5jA8eNSFsQGJYtEoj31qtawjuLwnNDYQ1fDDKJqm5ZiqBQRQTrqUU57/9FsOn6yvSNoNNFI481M3TOIXYfbFUM8wEYR53Wi6A/tRqpidyXvu8TUHsWlwPWAKsOO5qkb/oIQ/NfWeefit2nZUvaSyPcW7jqtjs5IEo7q96hIbseJn60oSNZKtBizhr6aaJKDFZwDxTlbbLg634T1Kof7GjcphPc2ODUaCm4W/M08+9t/USPugMHpiArKKyG5dBBqZkYZK03bmBpUiFO9aJKfD7K0SjcCcSdBQ2T3mDxGgUpUDc5IOQdcxVWvWH8qNIOnl72abNzO0b0kPC+E0CsWmBHhYlV/oiAl6ZwtvrUtGH4aGC2V6M+ycLdHpat0iFxvijC14dXNPZcPWPumyk2zTjUwaT0suhOeU9onB15xrIkUDOqChuNNvMQPXO/cQrKG2+wdSpjZboWuDtj+ElbxXiX/DMMyCLLmXxz2nnWfB3AjwMi1pdb435WG3rV7a5mE2i4cmHNcw+YkXBHyc0pqBxH0FiOtGYWtTAJHpNyvpStC8mIhLq8Gy7rHoijvCdYMcgtRlchc275A/R23wB2HaYbNDV6lj4pa7YWU+vWfP1uTjh8I+csOA4K40IiNLK7/ZwqoWoi0wRGSxIwICx5/t6WmoBO/u6J8jFKiL0pMgVBkZFPY/XANqBS9eku8NfhTSyZ/eiYbKH4WKGkg23UY+Q0H2RC9lAcXQSFv7WjViEqa0H5KbNk6xC3A+IvJCi+0BycWP3gs7qY9hVF7ONA8XP8pTbXI4me/6nZ0GZyam68AXAsheMJOTR80g2jxmsj+PkLZRUFvDlceL9ehSzT0wezNufHgLYHgnMG8H4qvCP4mtWxGUbuNeHSCIZT1gBeOLa3QKT+whRwif803udJdc9OHRAOmCxEl9L0yCOLnwFI3onJ06aFH7A3R7TQTKzPyY7U7tcxDL4nNOs4X5XydPAzwqT9OKjKBfgCk4AdfeaES2dLWU9ztCDFVhShkI8Y9XVI6zqFHEy9laimO5e6h4cNbRuyAqt+Qjo67JDKA96T84OWvwHQCErrW/+Ox3slhe54THp1Rts6fqT1d36+3a/pV+9IQzTK00osQRLHhR/b1XaGIWj37Sfh6LPXb7hVT4hdW0i3yfbc9LZSOhuB06olR/m9e6PKUVJixEuAXbfSSYHlJpu6+WxfDBWkOoAqzU4xZV8TLXocq5BZOOI/6adHTc3Jwfr1fYtrXKz6esT37x4yCy0iD/rklzZN5YzTMA8jmnTv5/wLtKT3Hl4g1ljw/bEv3eZOM+GslMVW59DuLu3n+ez21tDAbl3EY9z5XHiP49D9fQ4PuA9WOZktH9FA3IqS1+QqBH/reUL6ldye6IEYy5wSFt/++zO3MuW+EO5Fh1Gf+Awoi4JlFApscQQDSdC3AUvwg5gqBprnmIRpjlNv7LTJzonTPGr3zploip6YYZD/vdSlKot18wp8yNNP6CRoYBfTtVdYlFO6SnIL9omRB1UK9SNRPM8y3XJWClt+Vcgm0S+9Dg3yC9HHgeY1YxWDG/xZmf7aB1BIEhypEmHgY3VQH+4pm+zIAxTvXBJoR5AJRsAap2XFNux2AxoFJZZ/4yqTRYax1hHV/O92gcpuEA+YmGm2HbAqGDNolznqYNXO1Hty7RdPnAYmMD9gplSTgdbVpDHat9fJVWhHAhjJ12lIbTmDP70eR5hVYS2cU2TW31VZ6yG6Fu20zeAzi/z/4Ls8XRJ6MZ4mTJ8l6rH8+53cksfrbO4mLb9UAmm4evKJ/ZTfOHnQ58OkFGy06OozC5VzWRGbuA4sdJfvs/27W8gd0eYsRFXfbklB3dgn+Bc0+S2j39ldO+9q2bLSwH+ovkbePj7Nb1hR3njzpVyLoYm/foFBmTDaKMb8Aw/eksBXd6ews0RSdZ4ZMIuyOru58/RxNLeOVdX7dYSwpCSRK+H3doP03wHkHSVNPbsxnGZTpaFoJYK4VAvaxqjFEqgZc6laZpFoer3HkBWagkXg0k6rgLUOIoSxwSHJ9E6zdq2e84Vz+yDYngB3n8rTO5lnc6SBPhlS9Z+6JHZeHfjTlppdY3WFCF8RWWw7XyVyrfu+idboEVTrlP1EDc2BYDvk1wu3JQW+XPSvcZ8MSqmsAx9y+nyHigXoMtC9+Vlr9C0Y18qnGZUixeadnsVj8H+Vu/UHd9JzdG7MI3LDDyMgeaKfo1ATzFApCXVAsrGf+LzYBGyWQ66aOx2ruYu6EnRdm/aJ70nYVmmwqpbRJxWWGUmCI+IuxziA2OcHzGnBf4cAvCrqUHUe62UMCpO1Mt9IsmYqgWj0TP9frsAMnNSM2X6CH6UZu+Z5lLpvZimV64zQSCloIgkEsZ93HM5VsGLwXormxrhiEqY6gQ01Tzkn8A65zPfeT1ayO6oIfPLw2w1GqPi2RLdzYg4Xai+GkzuuoDk2sr/mSP/aUaiPNBYbS+tAp2a0gEFCItChaZTc9kYvX/tpLW85KEsqSOZC6XeS1WaNuVOP44YKU1c5aJwSegpxuxOLBPC5kctKqheCopZIM5alNccONgwTS/dttxB79M3QhPcnysSV+7LjMJTSVoQmYy+mEXxkck+bFM3K8Ckti6ROgaWi9goqDeBRMFqU7nFxzpzoj+I4bH48r0SZ7/c5VnEifQ1SuOFePuq4dYoOqScca01Y3JsEpc/OYmZ+WZVqnLYmARI8XxHDeJVhHcOzAItLcoD4dUc0+om3ObiHCp+qqtkq5NH+ErzHG8i78XA8k2daVJqtuDBvnxqFXjxLB0/t9cASFe28OiBoKIIXayXbxGBTYdk+Hjt040SgYSebmVgxImaQMfRqVmM05IjvUGJJuAxPYfoYDPfkS/xegZwgsVzHCXWdcO69nLWO/hXjQjSOOtRYYqixJsWCWmioMsDS8uGgP1NTWb8+eSJJNd69EU2NzeJt9qojUbuT02X2yc5ymzCwC8WyqniXJ1sawgxASLPMpi7nYmRspbf2pJ6g+epVJImm8mk/CEPnSRuCFWNMQ==\"\n },\n \"piece layers\": {\n \"0b09f31a4206b8c42530dc7fb489ea52f4db6992b1de8c9e56dd1bf711d64af4\": \"OrmkL+1wt5DMFYBPxOPA5cH8hsrAtlIhJviOqd41/UovO12SSlbrFchvhvi5DVrS8CkjguZZ3kfha8mRcGGY8RAc/RPjlPTggnO/aWvkFfCtGWsLa8ABT3MQETa+aH2iYfah6XNpDHw5heTQBas94o4noUMRWj2vf4XeSUUYmCdIld3ZUv5EC/lkauDqKi+iYE86H58RLKSoqbGkns+IkwDJQfh9GBY2FRLtitEbukJqsBxwnHY4QWWoGlkas0FrcooYDBSak+2fcYFoipVLbi7fPZHKR+sJLxFjE8Sc7/hVXfb8usqNYn3EPPHB8jlMsL/sC8ZD5YUIj6iXgFWVR0hIvwMLLJ9f7fGRSmO/R6Z15aF+0YLzNyS9DhAxrxJv6VVbT5VKC6l1/QOSSKr2UvW9C78Sidm7/f+4K1yY0XiPWlQyGODUMrvbBlGZTPb0SVi/828r+YrofHkSv5Tz868yv7R2QdvJ7ldV+RrM5604W0MAN1Tip6BNQndW1CWt\",\n \"26c6744b0b3245ed23691732c22f95a9bf074f32a49ba5c9feece809347e889e\": \"93cms33PbZZTwmgqv5mck+k2grLln6vNXYm1D0H9sr626dMzmAdfQif4o5BAAMScWvQhvRYscGWk1tjplEPvLU5U3OMyqu7tVPITvQ1w1lw7kcozYN4t8ePhlcR/nrE75oHF9Qm9xJAMts3GWvYxUROFkFilCLZd82NQ7Q0c6RKBNy5+y67rQ2VD/+AJFUuSBKUZSD70v12TdSRCL6T2xXsf/3G5jRxwaOVPsfnwinyHHz6FShzXytspL6XSAvXOoxH54BB8RmUgqyE+f5ocs1I+jpnx62UGPhIeU9por+UdwIjtjCFBbAYCXnMXBPfsVPR0HcsDgzcKwoWzvap7gJOJWhRRcB15OZiHQL9KJ6GRE1/ugcoEq2EJbw1Nje5Pzm8TXIEsNZyPVa5cN92E19hFFaOM1ZDKgq4lZgMLu1hrfvaeisk2TyI2TF8znoVw9CJCA0XXYI2e2XIH/3zXyqAYBn2h1un8MFpgBDkU+JblBiAp2L8smM7BBb6IQVKQpd86WXtrpWv5rSld2R94H7AUhc8xkaTZAa2vD8sEackjZBC9S02hWDEpGNl0ohQTVikghfqiW+zrOn7n7CIfcTHOp4SVJq7Qpi9ttuyILC9e+QI+MV888XrNcB29b7Gbd3M2/29mflYtJ7wBneZZdtlQGxgFXD+Z/pNktW5znVu7MzTvUhiitUbq42r0+4aXIa7TprFx3vsnO2IHMvKi7A==\",\n \"3b458ec67b680d056b9f48d2920242bd812249cebd1939880340e7d2e136fe0f\": \"nTFIf0LPwCPoNT78+kgBFHGy5XDvp5NA2p61K/z22PXj9kdCi7wPdvqK8jL2fVqwde66RhXHGbT/Wlutw0isomSetuJEWZ9A3F/TKw1rSjKPGz9hOGa+UfxjjUMX84QfVscP7GVGfcegWQTM87L2o/Q9VKMaQyNPUldSCebK5fSoggX+7LT+5d1U+Z1gaEI/GiCm/D0tfLh6ddXQhybKgZTzoP+zvDor7Mqu+4pGKE1eJHU5h0Iw6DdgGup6vfoOcTFY0flI4DUHi5JMR67XXadcpa5sslf8nZ0AyQC+PEe9+jI5yB3Y2Lj5V3Ah9xW3NJXruPOIZjeqCDXnZ+/Il6VuBvzhB/16jX+N1PFWNDfTP5dPGBaZJz15IYLzPUCC\",\n \"3ff20c6f038c048f27e428499498e1d19488f8f934df6632d5d4d827ccf3f3f2\": \"pUy003/UkvEJDvQzN5jXhX2B5truw0pqVsMHdPYMdqepwWcqZEBrtQvOptgzTBmxBmpyVUPoGed4G1ls5CuQOl4xV3fww7zU6tA/jUAZ2ufWbAfeIayiO+LdgX290qihHmni5a1MiGgL7vHrINB+BBnYJ75BSOFJ+Kcb0J1hBgPCleZZSHUe3y4LBo8Ten/ocrGbCwPAKzOr+g3sb3gt8QI9jKvB3e0VKGBTF58QrjoihLIdPiV/F1W55CkQXidVijh9FocJLESmWdf2CFm4mNOxRWSrOXAEOLuNwgs80pg7uL0qnAqr55gJbZLyktaWpP+cVNh5DaW1I6WwjCsEMWLJKvyGyWN74ibh3taHnGD5FFcw8ytSiFBcTeWDZl0pAuZZaxZgq11l8GuiFJjaMHzWhTixWoqOI3lZ8TUx9Yg=\",\n \"4767dc7f1b8d2f6aaae276034cb2e2c6c656af56683e148f0e54c15382273245\": \"X5RLsarlc9pADwVgp2d7PP3hZN3ZmYlep0pA2FuskDlbKIx3Rm0aNQJO3I0WeALY06cjog7jS9JXMMPGOZjSwfTPnvqPlOrpBmaR0/0k4UsHeV7Ma4D6Bs8TRdGgGIpmL4mhG19GMmNATTaCSTleOdkxLWg8/ikCEbk87hrqjMDTK570+4Z/37L075ZbJTZ9PiBv0d9otlcPwFwcAG0n99SCx7awBNSolAofK4aduCt+p8TffXnfeG72Ng2hZVCDIIKliAoguzDaGly0iyRkD9yxD7LcM6FTtcCpXYEfQBiBcVkQkZ5TclFCE7XPNpMdtu1ft1BPuisUcqDp7OYdGtvnHkjTPwuuYBp6XIbHYeIDIWfatuSmlzP/abATYrkxDSIHJTBSdOnTkY/9kK60QZ6wtbd10RpAdMcFs1QeMaP4fAvN1GrHwfaT+FBuu31S7MICgv6JzSkpRak9DhD9xoyG6H2fTwWPBY4s+1eLYYCwcwf7n6ChO4VHxZI9Yqk+aGjO/PYTlohOGDg3VeHxgGvVEmoDt+VA1nFtqvi4UPg0P7C914o4FzQrZjpqelSMKFCRlW5uWZnNH1eIe0XA7kSr6POILRwTYofx5Pl23MuDjsg+YTWcTukUj1pKqwuo\",\n \"4c94d4d2d20631afa6fa4d4287af4cf5c262c00f834c027c9df3f711adedb2db\": \"DyuboUHYom86yA33liC6obKdfKiCuOiyTlNTt5ACMtZF6TRLlaolBIUHNuLUnUPbfTTyz1cKtR/jFWwl563V8JhmXwSx9EvlfbCwIFulQBWcy9/35DHrRoIcwsUvB2Duu2Kfcd4ICOKqVvzOMsIR2+A1C6Ar+UCCADt5hna2ewVAFlQ/TM5rrxNioKAtWgmowt2dVrmng1dOX5cq6b9R1gQOB2byxyBbQf6oAK1royKWOyLMgVjjnGxLbpkgAAQhyhp+ja7MzGYF+mYo/ohSpjok0tfm0cEbG0k4s0Wgqlr3TMLdjyLTHzVGsPywKTwz/kUtrB3JKMWoxIqBjqWjTf+judP0abSv0/R6C8DhLk6x6oKuFeClww1cjoHfaP+OnSMHjZDPTw7gy+9oKVT5J5ByCWrXkUi760FhEQRXeEG2QAZkJTSgnNfVEEU6n7oU2Toslqh3SpMGjgjbZDveCxw6VhEP6mhZe6QyZ0fSHbaTnlIEt53jrUk4UNzEljVqqb08/csCA06T6mmBaAy82VBj8gRLSzfsWc6mQM2K6cG/UGd4NxdFPJgHMReLgU/xYiAiWhurTbjNhW0wwS86+6fbCPehWnvQq5rLDjtt44yohRMlhOIuPNgQeoQ5YUrtB/cQHki6Wg8bc4OyKMt8mrRBqR0ef5SS8aVt8Lt2TzT+AEp1IYwau6BEO59JkwqhF5F1uZO2+jBB449Az/B+O6JG2vjOhfnAw7aJgkJrUZICgzPkIzMfXEGTGmDzBI5acKeGeVt2Husv7794wjbdw5Lnz5k2vTogv34wNhYoSsUiYpFkHw5iiYERu+XfznAs8Zu2iD1oVRv4MWF8fnYJP8nwjOhSwrjBSx9HRaTicrF+sr9wRZfehlk14qN9E1Oq\",\n \"661b7dc0bb901d026370be0b73e87fb6a95479dc85bcdefe61711d8fc5b236f3\": \"gATN5ZHot0T6R5YneYJPrP76oSoC9fI49GtMgK2kJq0ya+06/wZZ5+RgBQoqFOv+5uio41M0wZoPyz6NsYrKFu/91+XGXzDoJQMsTj0PJFL/uXRyzfCtfNk7tfAGrEvN1Jstv3MVyPogT9zowv4U3zDpcPY8zjpfu46LkHI1XZyu6Uh2t/vrc55UO0QgSmAwr9z+mcX/up0n0ZaaTGNOESyHATjBon7HMt2ejPQ1MCBEtjLRPbhcdhEyJRW+A85aqFeqUNOLqTspBvqiio5peWQyQd0lIw/M3+Fi4WNJax+fR+DB+VPjKlaWscyje05QtNu42RT2iqB4L11jn0pdvw/Xfk3ooI+RBJ00u1AeSSQqZiY65sP3G6pTXbxDb6EnTgj48g6avUnyKMJCqHEY1/RhvDakG0n41l1NAT7vh7Y=\",\n \"6bdfbd145a64cabfc9b0971f87ae69c93bd1847b0c48351ed971c1fa4e9db97f\": \"SVct+bzPEHObJKHC3ZuofpIzlBylCSOFU8qsaGF2y7RrKZrjhlbOZdu8sE4rXUDn1csYAKCKwJ3cV1y3Ylg/el3ZO7d8syTwukJfpMfJE2yybMbQBlT/+AY4KncrT/IOD+3C6t88jOjaJW/VLiKY/RzeGmfyw3vZzVvFArsSLCxAbcrNIeATbLp0MJ57YOpgxiFgPU9+JUdMcN5PZtG86aTC8t+1Ky7MOcb2HzNdteqfVG58Hkl51l/xPFCHYZRTl3SkoOVy1+B8nEj0W8IS+1E8s9RemS4qyN2p2NZE9JBQKGiOTo8zTcyuhZPB9yxhB2xnfyCQenl8FwpfkQLCAA==\",\n \"72436d6a0686a9a83d782445224036c71dde61e1824ef5ea230462ad95daa11d\": \"slBceVoM9U+QRS9g3QIT+z/C08OGn3aAuwBuSSKGAzfCsDfRGCkY9IhFtQUG9wSjyrUU0Mh5oW6awq6aICOe/KQJCQ+7mM59RUBCkMbuME9Cr0ut2LlqAs1se4NtKgcMFwGnTtmy78Qu/q08tIAFQB60Y/buuHOaCtsxpWhoB3phA3eQRykDaWLmphUlXoYELeJP2T/C/5ZXENVQz79F8uyXTfTZj98iLqOT+8SxOKA7PsZsnIDRLWGWfgOic/nAZh1wJMIBRw90qFUmoYdQKaWRwB1hcQTObEtZAF+n8qxkRfHCbUnoNJTswB87QtG/hxaYclaVRZaCwgoLvoqYx85e+XGZEI83+HHxwto4xN/Bzj/JVtk2HFbx/QLCdV5rOtUKlJhsYIuOK2juRKCaOLSq5NghVf5alfy9qIjWWseQDHdPDiQB1mck2lrpZDJ+Das9f3b+ce4pLwxtQRW1JLOBjgBi7HXelPyu5lMRlqZcwRhNFZ4DxrprmPYmw0KeScndn5LHJXsDC1JA238+wIWP29ZF4QwLWvMPkn4Wfe4QYmBpeNINhPJjCwkhQx90Y20VBaJGKYK8/Le1J3VduaJ3ciYxDYcd7WW0ngaZiVKZ0y7pjfqC+tLT5zq+GQSGNPNXogL+OEaFKYVd7Lz0PfG88uaIkYVq0H7CDAjZwJZSnlCaT85L0Kv44jouiIKmfO9QX8zDS+QxLAh/Xy7VAw==\",\n \"736490ac9a57251d5feb02bb8e17d0f29e761993bd604ebece2f66e8cfc24a76\": \"KyDTfIYZYSJe7qMcrgvE5sUiV+0eus85j2/q76tIC8603rr7bx1er5UDI/7srDKX0/ah0ZdGtzleHKkkAuVwCE73C6iDxwEI5OESBF10mHRzD2ShejpsUNGx2wGKSNBl3m4XFs6Ta84bpxA9FvbbeBPsMvH8k1+fjxee4LqYxghlBHAOyMk0zMmvVGHN59cG7jnDon0o172flEf+UYrOnom8ZDi431XQ7wu5KVpfR8YqCjrAfLfPMhymEv2K17Kf\",\n \"7ad85e1400e68597e006040d4d9373d2a05d9b2ae13f14dd9939317d33ea2c8b\": \"/5ZiHJz/da3YhNv57jfG+6W5p++ZnjCPsy2QOJlC8m1iN2bmH3ygJtADgZZjbBq9lMg6qbCwEVfHzVNSgh6E62zNiPrHkguznFha+8orialK0xQe9TVnoMAS1fdMtFPkWFa8zKVXlLrmEiXa6MMDt7LmTGDOw4AFmIgTZ9+cQyJh+UvZYdYyO5xl9yVhDIK/HNSkhIe/E/gkMTBm/q+mXfER+uPLq1xc4eqykA+FVvJnBd4rzuDABEFUJP+sVqxqFFY28Ue+SSrcmHjnf8IL6bXem9lUcLoS176lXBOF73OIY9s1E+2vOM02HSXYiPXbsi+QE+cKrvs/PQ1gas5/EqGXDrW2ioYqebIX+Dw5re8xLvP67w3xrQh6N/UwyUb05FoKrgLFxisYn/NELkWiNV66PFccfCh4K0RYZUW8SMsHcwAEfQRst5ICX6H9HI6HUqkk++FQFWw1xmdPbcBKCw==\",\n \"91b3f6f33d7c2cc4752c3b6e79bafe7ec2d3e03c31ae3790a5c132d1a9b93f84\": \"n2cabuVybK3NWw3P3/XguQcIwT/JKcvTwjfIWqfqHQ8Sd1QeMY4bVjOol1ubEWU7BfhXaI9TS80wWoj7MKXCUdn+XkJXNh09vj1BoksVyiPjPT4powprR0MHolp1jxh+fN5uppVJMEtHYEMy1EcpU9zVAALQ5fwXL2XJ00+VhFsnoL+5NybXXGXB08Ihiy41rTkL/eDf9wS/xnDAooYsO+cC+ZZZJ+v7Pom/3pRU9soMFNJOKct1bsb7uuT9prOnKF4ppNEFZE/GwMezOYK+71WkMqjJHexFaEWiYLGM8mU=\",\n \"a2781d81c2dc33dac179bce14390485e47f2129ba98ed4efbd4690daf7229d95\": \"cy1UGgsHtzk1YlCUOzYxU5TTrIeXX7A6KiSlLj0ng+HzCKmJRaDGi0oqbkHJJTxA2UPrluSUJ4+CAhOYw7XZwHPVW8jh2ywDtSABmQvXEulGVJ/knxQCMCbnZEs5Sp+2J/Wk0hp6kJp0lu4Zsqts/pk1n/GiWxfN/1XZA1URfMu0GO4oWPDIPgpqFTKtUR0KL+AICQIo/7iKceDKl4EQLw==\",\n \"b27df2732173f94fb73fece8d7ca2864d2c027d5186f078d30c01e4310d700e0\": \"AwIURStzYR9k0OBxqnDmX1TL+6LU6aS9QO3blOLQuVh8MU/2F1KcBDskSVtkrJwfROiHpe9E8WCDMD0qbFRb4AMFBZhF7HeToUj175XtVeLU5WVWK8vB/gumLvQDTZqWb9tBwvcs8dGwzdVJfTNiIyIUl8eAVXtDEoICFgGyt1N85uGsl1BXTsY75p8pcKwAm/3wBVqZHPWUxYyjry1AKeCMCi09CIAn3HSD+65ewi9vwgNuhx3MZJQ6+3YZcHLWonWu2aGTHCPRHy7ERJ3EMgk3l7BDLoXLpTqe+/vRYXm7q7VwHyc0xEo6VeHLtWyeeUGUt23zWfxt9l5hHFCJ2ZvQVbJxYP49QCkvUbOmeYv7HAuWzl5f217lD+tnhOJ6AxBhMVZjWoy+IMg1y2C7mJ25nEd2J2yFfHf+WSy1h4d9IcPbYm64MSWR5TCnHKb1MSt4xdCjjTi2HP/8Zxhb9A==\",\n \"b6145263d70c69c630d59dfa07856663b33e5621a62a33d7cd706c3aafdfe2ed\": \"wfe1c3oZL51D35knRIf54tu+pUgpVC+Pitr0bOoSKXSKeKRvqfGE2aB8VbQLoEB5DjmEYteWfKtJBqN3i01k8aZiKfFLM0jpad/3LAH0ULeyMLRGPT0QTfuCzh46QjiSMa93Fnhf61OzQxqmt1A23pxwXeqwivAsehtrCthNeWncGiRl/8O/pMmyu6h6SF7LtfE7w+MaxB8m1MfQBvauhoTDOdRFAAlKm4wFkAHxZZlAixiUvKOchdvG31viM+OCyibWkTmkSlXVFWCp4ju1ndjS5jjzlPAz4lRd/D8YYoGEbUlK0WF7GNR8B+grTRE7m3CTLQBkLvtxsfPfR+TLEL/iPc7AQ4ZNZ+4ixzkWPISuvu7vZb0xByx5fgoWCPJDiRMprGx+GG8FjcMAI8Q5zpOCBVr5S+tQZpF6gUQJPtLXcHaWfXGy+AWGPWzEQQB4QzutKPTkZT/GteT9I7To+tKSsTv7bnWcfZrtBbOhpBUHBAl02W9vcPCwwgRpDLiWpGJfoziX4bmmqq3PlAdSf6D7pvOd5NJ5gsXFayZJHrwgZ8fO1zO+CvBlVEsTSEfOYxn6k4h6igW5Cjo+hPXRlF6tUi+opqd3oJK+AvER8exB4V3K4pa0eUO7ICA9PViKrX4eeKwRSurxzthIxR1SZyRIu+u80l3Qp0Vjn7Rs9ZY=\",\n \"b9f50e8baac6b38dc2f84e93387b8c5e21c334f54f8772cd5dc819e88c671c6b\": \"mlA7x/hs+LOUdTamHzEJZQZelGfXHa0EIXd8VZDLe1tnCTQgWmwYXfhSeJ6Sfsvz2J0t6jcLHuYM1JenXbNCnWA1sblB+D4aHQs/GWbP15cOKSwp3Vzr4m4VdrbPMRXdHFVn8cE/QRa8akYhme/uel68R1RyhgqvDZOsD1Ml7EigGtsKa0wzu9+kodzCCtCI+qruIUa64BxQDLTcW21fGpLeQZeaDJAIr/DrzSs9s/z0OjI1XP7Eo8VGGLFn7zO8dMT69LLyD5XPRbYIz50+/AV/2+Bn0DaBYy+f6AZJVAiCVEj7++2CuDIyAzNltb4SZSRH6fjyrmqDIo7v9PfIy7iXvTHwuJweyfEuDF/l7iKZsWjMlLT77vseIiaDqCBsXuMPg4cZCF35MXmfmDObco0zvUBxszujKyrGdKxx58bRnTgPPFh4h3lsDwKzJu23F60vsG4V0WNQ9tY2tiePfA==\",\n \"c67de66bc0c2c41fce411d351d82dff8afe809e5206df6b14d536d4ff4c00dbe\": \"+yKBmhJqkSsy3at/jXFIFvjZbUPT6ayJHsBtWeIP3mzHHflO6MUtioZiVQ2VnnA/PnR3JelkcwKwMRXmfm7i1zhBM2Awl0WlrqImvM2TBvmfMVPdpyWwejTwa/+qI79szw9waLwWYOdmu+jAwXk2hpE5naPvsm2lRuz1x1ehpyA=\",\n \"d0063c2d1f1c5e3b5e163383d5f5a63ac983be59708e0d695870442cebbe37d9\": \"Ngl7rHoMejoI0eGHLHI9mb0ELNrRc7h4EZCiaaiO1TQSgBX//vPdlS6pptsB62ocxGacCH+GrIhH75QNhwxAEWwBWFGDFofCVii4EctiGgBt3CpQgF7Jx0ce5AGxvD2/PKuV5MribIC893ftswL+NQFsT8Vp3N4O+nvWr2Q1/5avfGwkblyHAhWXIV5w3R9uYht94HxH0EjYZrDKCzkVr2ZypdlMVeStnJPBuJG5g+YcZMJBGFJvMxzYs95AeluXmh/dIIEh1WzPzVhaseC6q2z+SiIV5ZJtrH8deVNI4gOd6dOG5VCVjEtjwf1ewCzoYoma14wCm5gf8/4mT7FxNZ5zuB4QjBz0rJ6d/iQ2slugGU0syxnXKBLIFh2+Hx7WRA092URN/32owNkFVaj9OAg7pGKP97gdmKcqChaRoRLjgwJXQ5dr1YbLyG0qzhIUA3Vu1PtPb5qWBOykTxh3lO1ylt4Mlo2M9q7QjqONPfwXXOmttB792hOfERKzgniofrq0kwKEl/Yd/QhktOkrbrKUs1Xco0R3365y4kmuIdes3lws17nmWbu6fRZvjGrC6Zu3ZN7QRgxIpRWd1R7g6MkHnOBfz4Ws4yCheJorn5XgmbamnABpo6t1mwZEk7/6\",\n \"d1c3d80bf13fd42b3e8582a15a21172d23b80247f3b6ca85ffa97f018db4dfcb\": \"XldIzkVQ/K0VVG2P0RElCMdWn9S8BZSDOC5aiNVoz6GRXffE8J1looO+ZKsmT2oV8KkcUmbaHnxaWNixFC4Q3LozSkrcAjdnMq0MZxRFk/WVQmsDLTuUFEfKT6s6j9ERT/cTcH7Qw0bzDRzY8H3Q91qdw02RK9C2JWHqWdgxn6G0u8gIPFom7atyZb6eF6wpbPu2y+hYtV+KQ+WKbajiji3RYxBUEmnlsIA7TwaKBNRbL8IbmP0iX8o21yI6BRpPLQ3dfHz1J0FWdnvucbZaN63DLkZVyy0RnJ4jKULGiYXTCkKCFri3kNib799tEsU9JVUq46V5hKdP8SOxarWv7BCYhmvxbH3O53OVEpjmCALaVHtrD/S7R4R7qsPiTk290vk7edzbbj5NyoPsXJPXGDX41OwipCoIL6TMulboWVWU/ZjTSh/bH5WfqPdLJDhKx61AnZSpUPJgJroNdzojjw==\",\n \"e5273e3a62dee1a16328471a471f3a361eee0de07de68a11b6695c8483a4eb88\": \"JlcL/yfbL/d+1bjsMMpNkCN9AoNtQSHiecicfzxrpep2Vzthoooyk3zGGyrStrhmxHU1DgfoyW176op+3stFf4InqNCBBU2dciGuJrfEcmj7GuWi7eWVZWOcsZOrbOc07dz4BkaSxjPfCkxShE123IBOdz3t4Lot1CD0BsE8smVFF3qp6x0SFYxfofNSIU5T2gwIRE3HzJPzN1tnsV98lhaAUU/a3YUofqrgVNPAIrqZSH+bAmdGhxPsAJNzW+cq199LRjHVpIY3n41WoujCzZK3bF3hbRtU5hxQK+zqMQnSskDlk/34arHlTPRW+0z8hIIMcZjq+wzxxLBdcIim9hi/HsQjIrQjS4kdFwHuRZ7Mnjv0c2KLCui36pYqjAaMYhhIh5HOeyorUJY0T9ux08f5jE5caofU2GZu9pHZZjk=\",\n \"e755700c5bcea4905a1a3f900351d8a5564098bc081e6754e379f805728d190a\": \"O51c+F+qBXvwiodQC7gu1Rr+OfdGN4VI8gsLkqaiiGDyS3znAOXh0/0NF4+CVA98s896hwo9fgRMnLqOh47dkQdpFJVqA8GZS7ye9tf9kQIG46IfkhrKYJNoeilTs0rJC9MI9qK23EtTiDEYPjK2nuEvnCx67Wvj8uyUZvJM152LC9Qy/YdXw0uTP0jD/5hLDjOHWgNGEt4lalejai4yPR4xh4aHAZi8J7KNCdr9WnfQeTv9r0g7XVacs7+lOHc83LzyU2TUdgqlCULob6toS6yWMuoCF1Hpva+OTUyNYIHA1tywiItg4/frDOfdx+wS7SmUfTPxwrDgwfYKRZLT8AnBgScna9JdcDGOF3Vx4mM4H7E2RXM8b9hc+hs0XjsJmtmUueCm96U8Yt9Jj0O9eWVB5Huj47cF4HfJA/gBoOESUMGqURVMjvWL/XiX6+epAdNvySvRTtnOZpEfTvQHBDu1l8JzF7mVqCOOmIqWnS8IDVuQlZhcgtCKn00SWDal1i2RbbqeJbYoEQtCWHkwOWi2Uy37gj8mKZBNxMURazE=\"\n },\n \"infohash_v1\": \"59c586143f057d74f28f0c423b0b6e5d39317838\",\n \"infohash_v2\": \"84c40568b01503d8d4baed762ae8aa3c096ce6bdfdc7ede39fe9c6ef531f2de3\",\n \"type\": \"Torrent\"\n}\n"},{"location":"fep/d8c8/#abbreviated-representations","title":"Abbreviated Representations","text":""},{"location":"fep/d8c8/#v1_1","title":"V1","text":"Minimal
{\n \"id\": \"https://example.com/torrents/mytorrent123\",\n \"infohash_v1\": \"1ad02871c78eb1c2934f46de0c7ffd9ef9ee4083\"\n}\n Expanded
{\n \"id\": \"https://example.com/torrents/mytorrent123\",\n \"infohash_v1\": \"1ad02871c78eb1c2934f46de0c7ffd9ef9ee4083\",\n \"bencoded\": \"https://example.com/torrents/mytorrent123.torrent\",\n \"magnet\": \"magnet:?xt=urn:btih:1ad02871c78eb1c2934f46de0c7ffd9ef9ee4083&tr=http%3A%2F%2Fexample.com%2Fannounce\"\n}\n"},{"location":"fep/d8c8/#v2_1","title":"V2","text":"Minimal
{\n \"id\": \"https://example.com/torrents/mytorrent123\",\n \"infohash_v2\": \"d655cc657b0b56975c2596ef1c493055565daccaf2ae29cafdea22bd7cc80e6a\"\n}\n Expanded
{\n \"id\": \"https://example.com/torrents/mytorrent123\",\n \"infohash_v2\": \"d655cc657b0b56975c2596ef1c493055565daccaf2ae29cafdea22bd7cc80e6a\",\n \"bencoded\": \"https://example.com/torrents/mytorrent123.torrent\",\n \"magnet\": \"magnet:?xt=urn:btmh:d655cc657b0b56975c2596ef1c493055565daccaf2ae29cafdea22bd7cc80e6a&tr=http%3A%2F%2Fexample.com%2Fannounce\"\n}\n"},{"location":"fep/d8c8/#hybrid_1","title":"Hybrid","text":"Minimal
{\n \"id\": \"https://example.com/torrents/mytorrent123\",\n \"infohash_v1\": \"1ad02871c78eb1c2934f46de0c7ffd9ef9ee4083\",\n \"infohash_v2\": \"d655cc657b0b56975c2596ef1c493055565daccaf2ae29cafdea22bd7cc80e6a\"\n}\n Expanded
{\n \"id\": \"https://example.com/torrents/mytorrent123\",\n \"infohash_v1\": \"1ad02871c78eb1c2934f46de0c7ffd9ef9ee4083\",\n \"infohash_v2\": \"d655cc657b0b56975c2596ef1c493055565daccaf2ae29cafdea22bd7cc80e6a\",\n \"bencoded\": \"https://example.com/torrents/mytorrent123.torrent\",\n \"magnet\": \"magnet:?xt=urn:btih:1ad02871c78eb1c2934f46de0c7ffd9ef9ee4083&xt=urn:btmh:d655cc657b0b56975c2596ef1c493055565daccaf2ae29cafdea22bd7cc80e6a&tr=http%3A%2F%2Fexample.com%2Fannounce\"\n}\n"},{"location":"fep/d8c8/#reference-implementation","title":"Reference Implementation","text":"A python package with PEP 751 lockfile is included with this FEP, see ./implementation. The package implements encoding and decoding Torrent objects to and from bencoded torrents.
Install the package with python -m pip install . from the FEP directory, and then call fepd8c8 --help to see usage documentation
usage: fepd8c8 [-h] [--encode] [--decode] [--with-context] -i INPUT [-o OUTPUT]\n\nEncoding and decoding torrents to ActivityStreams Torrent objects.\n\noptions:\n -h, --help show this help message and exit\n --encode Encode a .torrent file to JSON\n --decode Decode a JSON torrent object to .torrent\n --with-context Include the JSON-LD Context in the json output\n -i, --input INPUT path to input .torrent or JSON file\n -o, --output OUTPUT path to write output to. If not provided, print to stdout\n Additionally, the torrent-models package will implement export to the specified format following this FEP's drafting (and this FEP will be updated with the version of torrent-models that implements it, when it is released)
Two string encodings (base64 and hexadecimal) are specified for binary data. This choice was made to balance space efficiency with matching conventions that are common in bittorrent clients: bittorrent clients and trackers typically represent infohashes and other hashes as hexadecimal strings, however base64 is a more efficient encoding for the much larger concatenated pieces and piece layers strings.
The handling of strings in the bittorrent specifications is vague, and that vagueness is matched here.
"},{"location":"fep/d8c8/#out-of-scope","title":"Out Of Scope","text":"This FEP only provides a means of representing .torrent files in ActivityPub/ActivityStreams clients. The following is thus out of scope:
Torrent objects from files and directoriesTorrent objects.torrent files, though the nature of RDF and JSON-LD allowing for future extensions is part of the motivation of this FEP.CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/d8c8/#footnotes","title":"Footnotes","text":"A .torrent file is often just called \"a torrent,\" as it is in this document.\u00a0\u21a9
Bittorrent v2 is a backwards compatible extension of bittorrent v1, so torrents may be v1-only, v2-only, or so-called \"hybrid\" torrents that have both v1 and v2 fields.\u00a0\u21a9
Install the package with python -m pip install .
Use the CLI fepd8c8, call fepd8c8 --help for usage instructions
usage: fepd8c8 [-h] [--encode] [--decode] [--with-context] -i INPUT [-o OUTPUT]\n\nEncoding and decoding torrents to ActivityStreams Torrent objects.\n\noptions:\n -h, --help show this help message and exit\n --encode Encode a .torrent file to JSON\n --decode Decode a JSON torrent object to .torrent\n --with-context Include the JSON-LD Context in the json output\n -i, --input INPUT path to input .torrent or JSON file\n -o, --output OUTPUT path to write output to. If not provided, print to stdout\n"},{"location":"fep/d8c8/implementation/#testing","title":"Testing","text":"To validate that we can roundtrip a torrent to/from the JSON representation,
test dependency group: python -m pip install '.[test]'--torrentdir ./some/directory argument that points to a directory with .torrent files to roundtrip--keep-output to inspect the created json filestorrent-models - a more complete implementation of .torrent files in python
DRAFT 2024-07-02 #350 - codeberg"},{"location":"fep/d9ad/#summary","title":"Summary","text":"This is a proposal to enhance the fediverse by creating test cases for FEPs. The proposal describes a Conformance Test Rule format that FEP authors and testers may find useful when creating tests cases as proposed.
"},{"location":"fep/d9ad/#contents","title":"Contents","text":"FEP-a4ed: The Fediverse Enhancement Proposal Process describes a process for proposing enhancements to the fediverse (i.e. 'FEPs').
However, this process says nothing about testing implementations claiming to conform to those proposals nor ways of making FEPs that are more conducive to consistent conformance testing.
However, consistent testing is an important part of an interoperability and accessibility on the web:
In order for web specifications to permit full interoperability and access to all, it is important that the quality of implementation be given as much attention as their development. Moreover, as the complexity of W3C specifications and their interdependencies increases, quality assurance becomes even more important to ensuring their acceptance and deployment in the market
\u2015NIST.gov - W3C Quality Assurance Activity Initiated
The goal of this FEP is to propose a way of proposing FEPs that may lead to more consistent testing of candidate implementations claiming to conform to a FEP.
For more context on conformance, see the appendix on conformance for:
FEPs authors MAY include a conformance clause.
FEPs authors MAY Indicate which conformance requirements are mandatory, which are recommended, and which are optional.
FEP authors MAY write test assertions.
FEP authors and testers MAY write tests.
FEP testers MAY publish a FEP test as a new FEP.
FEP implementors MAY test their implementations.
FEP implementors MAY publish an Implementation Conformance Statement explaining how the implementation conforms to a FEP
Project and services claiming to implement a FEP SHOULD publish an Implementation Conformance Statement
FEP testers MAY write tests using W3C Accessibility Conformance Testing (ACT) Rules Format 1.0
"},{"location":"fep/d9ad/#conformance-tests","title":"Conformance Tests","text":"Conformance Tests provide guidance for developers of automated testing tools and manual testing methodologies, to help ensure consistent interpretation of the FEP.
"},{"location":"fep/d9ad/#w3c-accessibility-conformance-testing-act-rules-format","title":"W3C Accessibility Conformance Testing (ACT) Rules Format","text":"W3C ACT (spec) is a conformance test format that can be used to provide guidance for testing conformance to W3C's Web Content Accessibility Guidelines that are normatively referenced in other standards and laws like section508.gov, gov.co.uk, and Europe's EN 301 549.
While there may be other formats for Conformance Tests, the ACT Format is sufficient for expressing Conformance Tests not only for accessibility, but for FEPs as well. This author was unable to find any other commonly used conformance test formats. At the same time, because the ACT format was designed specifically for accessibility guidelines, the ACT format may be imperfect for FEP Conformance Tests. So this FEP does not specify a strict conformance test format. Instead, it specifies useful components of a Conformance Test, most of which are inspired by similar subcomponents of ACT Rules.
"},{"location":"fep/d9ad/#fep-d9ad-conformance-test-format","title":"FEP-d9ad Conformance Test Format","text":"This FEP defines a kind of Conformance Test that may be used. It reuses many good decisions from the ACT Rule Format, while generalizing the format to be useful for expressing tests for things other than accessibility (e.g. FEPs).
At a high level, a Conformance Test specifies
Each of these components may be a part of a conformance test.
Applicability describes how to determine whether a test is even able to be applied to a particular Input and produce a meaningful outcome other than inapplicable.
A test may have all kinds of outcomes, e.g. inapplicable. If there is a test specified to apply to an ActivityPub Actor Object and check that it is valid JSON, and the test is run with input of a fish, it's not even possible to check the fish for JSON Syntax conformance to derive any targets or apply expectations. It's a fish. The test doesn't apply at all, and in situations like this, the test run MAY have outcome inapplicable. The Applicability section in a Conformance Test specifies how to determine whether the test applies to the test input and, if so, how to derive the test targets.
Inspired by
An example of an Applicability clause for a hypothetical conformance test is:
This test applies to an ActivityPub Actor Object. If the value of the actor input's \"type\" property is an array, there should be a test target for each entry in the array. If it is not an array, there should be one test target whose value is the value of the actor input's \"type\" property.
https://w3id.org/fep/d9ad/ns/test/applicability
"},{"location":"fep/d9ad/#change-log","title":"Change Log","text":"A log with entry for each change to the test over time.
For example, if a test if maintained over many years, it is likely that the editors will learn from experience with implementations and usage of the test implementations that there were unexpected inputs the test might be presented with, and then update the test inputs, target, or expectations to better handle the situation. This kind of update is encouraged, but it should be logged in the Conformance Test's Change Log.
"},{"location":"fep/d9ad/#uri_1","title":"URI","text":"https://w3id.org/fep/d9ad/ns/test/changeLog
"},{"location":"fep/d9ad/#description","title":"Description","text":"A plain language overview of what the test does and why.
Inspired by
This is a conformance test that checks whether an ActivityPub Actor satisfies syntax requirements for the \"type\" property
"},{"location":"fep/d9ad/#uri_2","title":"URI","text":"https://w3id.org/fep/d9ad/ns/test/description
"},{"location":"fep/d9ad/#expectations","title":"Expectations","text":"Assertions about test targets.
An Conformance Test MUST contain one or more expectations.
The expectations describe what the requirements are for the test targets.
An expectation is an assertion about a test target.
Expectations determine test target outcomes
Each expectation must be distinct, unambiguous, and be written in plain language.
Inspired by
https://w3id.org/fep/d9ad/ns/test/expectations
"},{"location":"fep/d9ad/#glossary","title":"Glossary","text":"Definitions for common terms.
The Glossary for a test MUST define each of the test's possible Outcomes.
Inspired by
a globally unique identifier that identifies the Conformance Test
The Identifier MUST be a URI.
Inspired by
urn:uuid:93bafe14-b13f-4a9a-9d47-0a9316d50c97https://bengo.is/fep/d9adhttps://w3id.org/fep/d9ad/ns/test/id
"},{"location":"fep/d9ad/#input","title":"Input","text":"This is what goes in to each run of the conformance test.
The test input is the entirety of how each run of the test can be configured.
An Input may itself have several components.
"},{"location":"fep/d9ad/#example-input-specification","title":"Example Input Specification","text":"Consider a hypothetical Conformance Test that tests conformance with ActivityPub. An Input to the test may have several components, for example:
id - URI - An ActivityPub Object IDauthorization - optional string - A value to pass with each request to fetch idThe Input and its components MAY be represented as a JSON Object with a property for each named component:
"},{"location":"fep/d9ad/#example-input-as-json","title":"Example Input as JSON","text":"{\n \"object\": \"https://bengo.is/fep/d9ad\",\n \"authorization\": \"mellon\"\n}\n"},{"location":"fep/d9ad/#uri_5","title":"URI","text":"https://w3id.org/fep/d9ad/ns/test/input
"},{"location":"fep/d9ad/#issues-list","title":"Issues List","text":"A list of unresolved issues with the test.
Tests should be published early and often, even and especially before there are no known issues with it. The issues may be considered over time, perhaps waiting for enough information to determine a resolution, and then the test may be updated accordingly. The Issues List makes test readers aware of unresolved issues that may lead to test changes later.
Inspired by
https://w3id.org/fep/d9ad/ns/test/issuesList
"},{"location":"fep/d9ad/#name","title":"Name","text":"A short label for the test that can distinguish it in a list of tests.
The name MUST NOT include HTML markup.
The name MAY have distinct values for distinct languages.
The name may be a value that is not unique amongst all other test cases. However, test names should be sufficiently descriptive to distinguish them from other tests in the same test suite.
Inspired by
An example name for a hypothetical test:
ActivityPub Actors MUST have a valid \"type\" property value according to FEP-003c
"},{"location":"fep/d9ad/#uri_7","title":"URI","text":"https://w3id.org/fep/d9ad/ns/test/name
"},{"location":"fep/d9ad/#outcomes","title":"Outcomes","text":"A test's glossary SHOULD define every possible outcome that the test may assign to test targets as a result of the test expectations.
"},{"location":"fep/d9ad/#uri_8","title":"URI","text":"https://w3id.org/fep/d9ad/ns/test/glossary
"},{"location":"fep/d9ad/#requirements-mapping","title":"Requirements Mapping","text":"When a Conformance Test is designed to test conformance to one or more requirements documents, the test must list all requirements from those documents that are not satisfied when one or more of the outcomes of the test is failed.
Each requirement in the mapping must include the following:
Inspired by
https://w3id.org/fep/d9ad/ns/test/requirementsMapping
"},{"location":"fep/d9ad/#target","title":"Target","text":"All parts of the test subject that will be checked by the test.
Each target MUST have a test outcome. Each possible outcome SHOULD be defined in the glossary.
From a given input, the test may derive many targets. For example, from an input that is a JSON Object, a test could derive a test target for each JSON Object nested within the input Object. Then the test, for each target, could check for conformance to a hypothetical requirement that all objects within the input MUST have a certain property.
Inspired by
https://w3id.org/fep/d9ad/ns/test/input
"},{"location":"fep/d9ad/#test-cases","title":"Test Cases","text":"Test Cases (i.e. test cases for the test itself) are a set of example inputs and corresponding results that implementors of the test can check to be confident they have implemented the test has specified in prose. The Test Cases may also be helpful to test readers seeking to understand the intention of the test expectations.
Every Conformance Test Case SHOULD include
Inspired by
https://w3id.org/fep/d9ad/ns/test/testCases
"},{"location":"fep/d9ad/#test-subject","title":"Test Subject","text":"An overview of what kind of thing the Conformance Test tests. For example, a FEP Conformance Test may apply to an ActivityPub Actor, or another ActivityPub Object, or some part of a server that hosts many ActivityPub Objects, or it could test something else entirely. The Test Subject is helpful for contextualizing the test Input
Inspired By
The test suites that the test is a part of, if any.
For each test suite membership, the test should specify:
https://w3id.org/fep/d9ad/ns/test/testSuiteMemberships
"},{"location":"fep/d9ad/#conformance-with-this-specification","title":"Conformance with this Specification","text":"Conformance requirements are indicated by sentences containing MUST a la RFC2119. A FEP-d9ad Conformant Conformance Test is a document satisfying all conformance requirements in this document."},{"location":"fep/d9ad/#related-links","title":"Related Links","text":"These links were helpful when researching conformance testing
Conformance is the fulfillment of specified requirements by a product, process, or service. These requirements are detailed in a specification as part of a conformance clause and in the body of the specification. A conformance clause is the section of a specification that identifies all the criteria that must be satisfied in order to claim conformance to the specification.
\u2015w3.org - QA Framework: Specification Guidelines
"},{"location":"fep/d9ad/#what-is-a-conformance-model","title":"What is a Conformance Model?","text":"What does it mean?
The conformance model is the conceptual framework in which conformance is defined. It consists of and is defined by addressing at least these three topics:
Why care?
The key is to communicate to the reader what conformance to the specification is all about. The model provides a framework for implementers, describes what they need to build in order to conform, and explains the different ways that they could claim conformance. It provides users and customers with a basis on which to express their requirements.
\u2015w3.org - QA Framework: Specification Guidelines
"},{"location":"fep/d9ad/#why-does-conformance-matter","title":"Why does Conformance Matter?","text":"It is inevitable that people (e.g., vendors, purchasers) will either claim conformance or demand conformance to a technology. In fact, claiming conformance to a technology may be required in certain situations. Thus, it is important to provide a consistent and unambiguous way to make these claims. Identification of the specification version, class of products, and conformance label are some of the items that could be part of such wording.
Why care?
Having a framework, by which to make conformance claims for a particular usage of the technology, minimizes confusion by people who are interested in such claims. Many contexts use conformance claims, including legal as part of regulations, laws, or policies and commercial when selling or buying a product.
\u2015w3.org - QA Framework: Specification Guidelines
"},{"location":"fep/d9ad/#copyright","title":"Copyright","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/db0e/","title":"Index","text":"hide: - navigation description: \"This proposal addresses the problem of authenticating access to the content of non-public groups. It is mostly intended to supplement FEP-400e. Only the server that hosts the Group actor knows for sure who can and can not access the content in the group. However, due to each object being hosted on the server of the actor that created it, it is not ordinarily possible for those other servers to restrict access to that object only to those actors who have the permission to see it. This FEP defines an authentication mechanism, \"actor tokens\", that allows an actor to issue tokens that serve as a temporary proof of group membership for other servers.\"
"},{"location":"fep/db0e/#fep-db0e-authentication-mechanism-for-non-public-groups","title":"FEP-db0e: Authentication mechanism for non-public groups","text":"Authors Status Date received Tracking issue Discussions Repository Gregory Klyushnikov activitypub@grishka.meDRAFT 2024-05-03 #313 - codeberg"},{"location":"fep/db0e/#summary","title":"Summary","text":"This proposal addresses the problem of authenticating access to the content of non-public groups. It is mostly intended to supplement FEP-400e.
Only the server that hosts the Group actor knows for sure who can and can not access the content in the group. However, due to each object being hosted on the server of the actor that created it, it is not ordinarily possible for those other servers to restrict access to that object only to those actors who have the permission to see it.
This FEP defines an authentication mechanism, \"actor tokens\", that allows an actor to issue tokens that serve as a temporary proof of group membership for other servers.
"},{"location":"fep/db0e/#requirements","title":"Requirements","text":"The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119].
"},{"location":"fep/db0e/#fetching-content-from-the-server-that-hosts-the-group","title":"Fetching content from the server that hosts the group","text":"To fetch an object from the server that hosts the group (including the Group actor itself for private groups), the requesting server MUST sign the GET request with an HTTP signature using the key of any of its actors. It is RECOMMENDED to use a server-wide service actor for this purpose, e.g. /activitypub/serviceActor in Smithereen. The rationale for this is that most ActivityPub servers only fetch and store a single copy of each remote object for all users to whom it may concern, and are responsible themselves for enforcing the visibility rules, if any, either way.
The process of fetching an object from other server involves an actor token. An actor token is a cryptographically signed temporary proof of membership in a group. Since it would be impractical to provide a revocation mechanism, an actor token has a limited validity time in order to account for cases when someone has left a group or was removed from it.
"},{"location":"fep/db0e/#structure-of-the-actor-token","title":"Structure of the actor token","text":"An actor token is a JSON object with the following REQUIRED fields:
issuer: ID of the actor that generated this tokenactor: ID of the actor that the token is issued to (and must be presented with a valid HTTP signature of)issuedAt: timestamp when the token was generated, ISO-8601 instant (same format as ActivityPub timestamps)validUntil: timestamp when the token expires, ISO-8601 instantsignatures: array of signature objects, currently with only one possible, and REQUIRED, element defined:algorithm: must be the string rsa-sha256keyId: key ID, same as in HTTP signatures (e.g. https://example.com/groups/1#main-key)signature: the RSA-SHA256 signature itself encoded as base64, see below for detailssm:actorToken endpoint","text":"Actors that are capable of issuing actor tokens have a sm:actorToken endpoint (where sm is an alias to JSON-LD namespace http://smithereen.software/ns#) in their endpoints object. This endpoint accepts signed GET requests and returns actor tokens.
To use an actor token when fetching an object, pass it as Authorization: ActivityPubActorToken {...} HTTP header.
signature, and transform them into the format key: value. Add these strings to an array.\\n, U+000A).signature). It is RECOMMENDED that the validity period is 30 minutes, and it MUST NOT exceed 2 hours.signature, algorithm, and keyId fields.signatures array.actor in the token object matches the actor ID from keyId in the HTTP signature. Otherwise, you MUST return a 403 and stop.signatures array, find an object that has algorithm set to rsa-sha256 to get the signature value. If there isn't any, you MUST return a 403 and stop.issuedAt MUST be in the past, validUntil MUST be in the future, and the difference between them MUST NOT exceed 2 hours. It is RECOMMENDED to apply some margin to these checks to account for imprecisely set clocks. Smithereen uses 5 minutes.issuer.{\n \"issuer\":\"https://friends.grishka.me/groups/75\",\n \"actor\":\"https://activitypub.academy/actor\",\n \"issuedAt\":\"2024-05-03T14:02:18.680404311Z\",\n \"validUntil\":\"2024-05-03T14:32:18.680404311Z\",\n \"signatures\":[\n {\n \"algorithm\":\"rsa-sha256\",\n \"keyId\":\"https://friends.grishka.me/groups/75#main-key\",\n \"signature\":\"w+W1nNV+XBvXi8sDEUZB7muWSSnv1mEE4tNZJqF5LeoxAstBMiBZi8dtHF+v+vXKVPWBAdZUKLS5CttmgZ4tvnvZAfsBztCjYLyiolVQ71IO2Jxlu00Xo9FDoSTRZ61tXdfWufuzs5lRjG3t+S1t1lLllBFmvPLg6BwmdEPvlZvPYnTJzwNY0ljOjickPqfyvdzIslmdYX6dPC0Ayyi028ZmR2SN1Vooc9vnUQ7GMPrlAZtmXgjCVGw5X/cKlAVvGECxRjJnkKEKiLp3lv/SM1UUhP3VRpBSFhXnRX/1QhTUaFV1MhrfDFgWGPg8ypIf6O/M52+iSpJyIOGepmjmow==\"\n }\n ]\n}\n"},{"location":"fep/db0e/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/dc88/","title":"FEP-dc88: Formatting Mathematics","text":"Authors Status Date received Tracking issue Discussions Repository Calvin Lee pounce@integraldoma.inDRAFT 2023-09-12 #161 - codeberg"},{"location":"fep/dc88/#summary","title":"Summary","text":"This FEP recommends a method for formatting mathematics in ActivityPub post content in [MathML Core]. Furthermore, this FEP describes how to sanitize and convert such mathematics to plain text, if an implementation does not wish to support mathematical formatting.
"},{"location":"fep/dc88/#requirements","title":"Requirements","text":"The key words \u201cMUST\u201d, \u201cMUST NOT\u201d, \u201cREQUIRED\u201d, \u201cSHALL\u201d, \u201cSHALL NOT\u201d, \u201cSHOULD\u201d, \u201cSHOULD NOT\u201d, \u201cRECOMMENDED\u201d, \u201cMAY\u201d, and \u201cOPTIONAL\u201d in this specification are to be interpreted as described in [RFC-2119]. \u201cThe implementation\u201d is to be interpreted as an ActivityPub conformant Client, ActivityPub conformant Server or ActivityPub conformant Federated Server as described in [ActivityPub] which wishes to produce or consume mathematically formatted content.
"},{"location":"fep/dc88/#history","title":"History","text":"Written mathematics depends very heavily on a system of notation which has been continuously evolving over thousands of years. Despite its ubiquity, mathematical notation is anything but constant. Mathematicians rely heavily on complicated typesetting systems such as [LaTeX] to layout text in their desired fashion.
Several fediverse instances, such as [Mathstodon] have emerged to host discussion related to mathematics. This is to no small part due to the difficulty of conveying and formatting mathematical text, and many provide their own typesetting implementation based on [LaTeX]. However, rendering TeX-like formats is expensive and fraught with issues due to security and implementation-specific quirks. This has resulted in multiple instances with incompatible TeX-like implementations.
In recent years, [MathML Core] has become standardized by all major web browsers, and offers an alternative method to communicate mathematics between differing fediverse implementations that is compatible with the ActivityPub standard.
"},{"location":"fep/dc88/#formatting-mathematics","title":"Formatting Mathematics","text":"The implementation MAY produce mathematical formatting in the summary or content properties of [ActivityStreams] objects, as defined in [Activity Vocabulary] if the mediaType is text/HTML (the default). This formatting MUST be placed within one or more top-level <math> elements, hereon referred to as 'a math element'.
A math element MUST contain one <semantics> child element, and no other children. The <semantics> element MUST contain a [MathML Core] expression as its first child, and at least one <annotation> element. The encoding property of this <annotation> element SHOULD be \"application/x-tex\", but MAY be \"text/plain\", and MUST contain a plain-text description of the mathematics\u2014preferably in the authored format. The implementation MAY include additional <annotation> or <annotation-xml> elements with other semantic information.
All elements contained within a math element MUST be MathML Core Elements as defined in [MathML Core], excluding those contained within <annotation> elements.
The implementation SHOULD sanitize incoming mathematical formatting before displaying it to a user. There are two methods by which an implementation may sanitize incoming mathematical formatting.
"},{"location":"fep/dc88/#sanitizing-a-math-element","title":"Sanitizing a math element","text":"The implementation MAY sanitize a math element before displaying it to a user. The implementation SHOULD remove all non MathML Core Elements as defined in [MathML Core]. The implementation MAY remove any attribute which does not contain semantic information (see [Semantic Attributes]). The implementation SHALL NOT remove any [Semantic Attributes] or MathML core Elements and instead should replace a math element with text.
"},{"location":"fep/dc88/#replacing-a-math-element-with-text","title":"Replacing a math element with text","text":"The implementation MAY remove a math element completely, and replace it with text within the <annotation> element with encoding \"application/x-tex\" as described in [Formatting Mathematics] and SHOULD fall back to a \"text/plain\" annotation. If a math element is not formatted as described in [Formatting Mathematics], then the implementation MUST remove it completely.
The implementation MAY surround the text from the <annotation> element with a pair of delimiters. For example, if a math element has the attribute display=\"block\", it may choose the delimiters $$ and $$, and if displaystyle=\"inline\" it may choose $ and $ to match the TeX typesetting system.
{\"@context\": [\"https://www.w3.org/ns/activitystreams\", {\"@language\": \"en\"}],\n \"type\": \"Note\",\n \"id\": \"http://postparty.example/p/2415\",\n \"content\": \"I have a truly marvelous proof that\n <math>\n <semantics>\n <mrow>\n <msup><mi>x</mi><mi>n</mi></msup>\n <mo>+</mo>\n <msup><mi>y</mi><mi>n</mi></msup>\n <mo>\u2260</mo>\n <msup><mi>z</mi><mi>n</mi></msup>\n </mrow>\n <annotation encoding=\\\"application/x-tex\\\">x^n+y^n\\\\ne z^n</annotation>\n </semantics>\n </math>\n which this note is too small to contain!\",\n \"source\": {\n \"content\": \"I have a truly marvelous proof that \\\\(x^n+y^n\\\\ne z^n\\\\) which this note is too small to contain!\",\n \"mediaType\": \"text/markdown+math\"}}\n This object's source content represents a valid sanitization of its content field.
mathvariant normal all displaystyle, scriptlevel all <math> display all <mfrac> linethickness 0, 1 <mspace> width, height, depth all <mo> form stretchy, symmetric, largeop, movablelimits, lspace, rspace, minsize, all"},{"location":"fep/dc88/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/dd4b/","title":"FEP-dd4b: Quote Posts","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.orgDRAFT 2025-02-21 #511 Discussions codeberg"},{"location":"fep/dd4b/#summary","title":"Summary","text":"This FEP describes the mechanism defined in Activity Streams 2.0 and the Activity Vocabulary for making quote posts, that is, Announce activities with additional commentary.
"},{"location":"fep/dd4b/#motivation","title":"Motivation","text":"Redistributing content or activities created by other actors is a key activity on the social web. The Announce activity type is defined in Activity Streams 2.0 (\"AS2\") to represent this activity. Announce is used in ActivityPub to provide sharing functionality; see 7.11 Announce Activity (sharing).
The Activity object type in AS2 inherits all the properties of the Object type. This means that all activity types have a rich set of properties for representation as first-class content on the social web. The Announce type is no exception.
However, the ActivityPub specification does not describe how to use those properties to provide additional commentary, metadata, and files for the shared content. Called \"quote Tweets\", \"repost with comment\", or \"quote posts\", this kind of extended sharing gives additional context to shared objects.
This FEP describes how to use the content property to provide this commentary, as well as other properties that may be useful in this context. It is applicable to uses of Activity Streams 2.0 in general; where it applies to ActivityPub, it is noted.
object property of an Announce activity MUST be a reference to the shared content. It MAY be an AS2 Object or a Link, either as a JSON object or as a URL.content property of an Announce activity MAY be used to provide additional commentary on the shared content.attachment property of an Announce activity MAY be used to provide additional media content related to the shared content or the commentary.tag property of an Announce activity MAY be used to provide additional metadata, such as Mention objects or Hashtag objects, about the shared content or the commentary.inReplyTo property of an Announce activity MAY be used to connect an Announce activity to another object as a reply. The replied-to object MAY be the shared content, but this is unusual; it MAY also be part of a separate conversation.Announce activity with an inReplyTo property SHOULD be included in the replies collection of the object being replied to.Announce activities SHOULD be counted as part of the shares collection of the shared content, regardless of any additional properties, as defined in 7.11 Announce Activity (sharing).This represents a simple quote post, where the actor is sharing a note from another actor with commentary.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/activities/aaabbbccc\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/users/evan\",\n \"to\": \"https://example.com/users/evan/followers\",\n \"object\": {\n \"id\": \"https://example.com/notes/1234\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/users/franklin\"\n },\n \"content\": \"I think that this is a good point and should be shared.\"\n}\n"},{"location":"fep/dd4b/#quote-post-with-attachment","title":"Quote Post with Attachment","text":"This represents a quote post with an attachment, where the actor is sharing a note from another actor with commentary and an image.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/activities/dddeeefff\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/users/evan\",\n \"to\": \"https://example.com/users/evan/followers\",\n \"object\": {\n \"id\": \"https://example.com/notes/1234\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/users/franklin\"\n },\n \"content\": \"The author describes the rock formations of Crete; here's an example from my recent visit.\",\n \"attachment\": {\n \"type\": \"Link\",\n \"mediaType\": \"image/jpeg\",\n \"url\": \"https://example.com/images/1234.jpg\"\n }\n}\n"},{"location":"fep/dd4b/#quote-post-with-hashtag","title":"Quote Post with Hashtag","text":"This represents a quote post with a hashtag, where the actor is sharing a note from another actor with commentary and a hashtag.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/activities/ghhiijjkk\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/users/evan\",\n \"to\": \"https://example.com/users/evan/followers\",\n \"object\": {\n \"id\": \"https://example.com/notes/1234\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/users/franklin\"\n },\n \"content\": \"Great description of Cretan geology; saving it for my next trip. <a href='https://example.com/tags/evanstriptocrete'>#evanstriptocrete</a>\",\n \"tag\": {\n \"type\": \"Hashtag\",\n \"href\": \"https://example.com/tags/evanstriptocrete\",\n \"name\": \"evanstriptocrete\"\n }\n}\n"},{"location":"fep/dd4b/#quote-post-with-mention","title":"Quote Post with Mention","text":"This represents a quote post with a mention, where the actor is sharing a note from another actor with commentary and a mention.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/activities/lllmmnnoo\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/users/evan\",\n \"to\": [\"https://example.com/users/evan/followers\", \"https://example.com/users/jeff\"],\n \"object\": {\n \"id\": \"https://example.com/notes/1234\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/users/franklin\"\n },\n \"content\": \"<a href='https://example.com/users/jeff'>@jeff</a> you might like this Cretan geology article.\",\n \"tag\": {\n \"type\": \"Mention\",\n \"href\": \"https://example.com/users/jeff\",\n \"name\": \"jeff\"\n }\n}\n The author of the quoted material can also be mentioned.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/activities/pppqqqrrr\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/users/evan\",\n \"to\": [\"https://example.com/users/evan/followers\", \"https://example.com/users/franklin\"],\n \"object\": {\n \"id\": \"https://example.com/notes/1234\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/users/franklin\"\n },\n \"content\": \"<a href='https://example.com/users/franklin'>@franklin</a> wrote this great Cretan geology article.\",\n \"tag\": {\n \"type\": \"Mention\",\n \"href\": \"https://example.com/users/franklin\",\n \"name\": \"franklin\"\n }\n}\n"},{"location":"fep/dd4b/#quote-post-as-reply","title":"Quote Post as Reply","text":"A quote post can be used as a reply, often to share evidence or information from another source.
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/activities/rrrsssttt\",\n \"type\": \"Note\",\n \"actor\": \"https://example.com/users/jeff\",\n \"to\": \"https://example.com/users/jeff/followers\",\n \"content\": \"Does anyone know where I can find a good article on Cretan geology?\",\n \"replies\": {\n \"id\": \"https://example.com/activities/rrrsssttt/replies\",\n \"type\": \"Collection\",\n \"totalItems\": 1,\n \"items\": [\n {\n \"id\": \"https://example.com/activities/lllmmnnoo\",\n \"type\": \"Announce\",\n \"actor\": \"https://example.com/users/evan\",\n \"object\": {\n \"id\": \"https://example.com/notes/1234\",\n \"type\": \"Note\",\n \"attributedTo\": \"https://example.com/users/franklin\"\n },\n \"content\": \"<a href='https://example.com/users/jeff'>@jeff</a> you might like this Cretan geology article.\",\n \"tag\": {\n \"type\": \"Mention\",\n \"href\": \"https://example.com/users/jeff\",\n \"name\": \"jeff\"\n },\n \"inReplyTo\": \"https://example.com/activities/rrrsssttt\"\n }\n ]\n }\n}\n"},{"location":"fep/dd4b/#user-interface-guidance","title":"User interface guidance","text":"A common representation of quote posts is to include the object of the Announce as an embedded card or other representation, with the content of the Announce object as introductory text.
For all Announce activities, it's important to consider how much of the shared content to expose to the recipient of the Announce activity. Using an URL as a reference, or including a limited set of metadata as in the examples in this FEP, allows the publishing server for the shared content to control access to the content. See Inclusion of the shared object in the ActivityPub Primer for more information.
The creator of the quoted content can indicate their consent to be quoted using the shares collection. If the creator consents to being quoted, they can add the quote post to the shares collection. If they do not consent, they can omit the quote post or remove it.
Servers SHOULD provide affordances to allow creators to add to, and remove from, the shares collection. Servers may provide opt-in consent, where Announce activities are not added to the shares collection unless the original author explicitly adds them. The server MAY provide opt-out consent, where Announce activities are added to the shares collection automatically on receipt, but can be removed by the original author.
Consumers of quote posts can refer to the shares collection at any time to determine if the publisher has expressed consent. Consent may be extended or withdrawn at any time.
Consumers SHOULD indicate if the original author has consented to the quote post, and MAY obscure or hide quote posts that do not have the original author's consent.
"},{"location":"fep/dd4b/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/e229/","title":"FEP-e229: Best practices for extensibility","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT 2024-04-02 #293 - codeberg"},{"location":"fep/e229/#summary","title":"Summary","text":"Current popular implementations of ActivityPub do not handle extensibility very well. This FEP seeks to highlight some basic requirements for extensibility, and offer suggested advice to implementers who wish to avoid compatibility issues, particularly for LD-unaware consumers.
"},{"location":"fep/e229/#general-recommendations","title":"General recommendations","text":""},{"location":"fep/e229/#ld-unaware-consumers","title":"LD-unaware consumers","text":""},{"location":"fep/e229/#normalize-types-into-type-sets","title":"Normalize types into type-sets","text":"It is an unfortunate and erroneous belief that objects in [AS2-Core] or [AP] can have only one type. This assumption breaks proper extensibility. Wherever a generic ActivityStreams consumer needs to know whether it is dealing with an [AS2-Vocab] type or [AS2-Core] mechanism like Collections, it cannot do so unless that type is present in the type set. However, extension vocabularies may need to declare additional types as interfaces that have been fulfilled by the given object. For this reason, LD-unaware consumers doing type checks need to take care to normalize type into a set, and check that their desired type is contained within that set.
For example, \"type\": \"Collection\" would be normalized into \"type\": [\"Collection\"].
LD-unaware consumers MUST NOT attempt naive string comparison against the JSON-LD context declaration. There are several possible reasons why a received document might be valid AS2 but not declare a @context. One possibility is that the declared Content-Type is application/activity+json and the producer is LD-unaware. Another possibility is that the producer is LD-aware, but using a different context IRI that defines the same terms. Yet another possibility is that the producer is embedding inline term definitions. Regardless of the reason, either the consumer understands it or does not understand it.
Given that including the [AS2-Context] is only a SHOULD and not a MUST, it is possible for some LD-unaware producers to serialize a document without a @context declaration. If the Content-Type is application/activity+json, then you MUST assume or inject the [AS2-Context] into the document, per [AS2-Core] section 2.1.
By default, the [AS2-Context] document declares @vocab to be _:, meaning that the default vocabulary namespace is the blank namespace. Extension types and properties can be implemented as-is by LD-unaware producers, and the JSON-LD expansion algorithm will expand term to _:term. JSON-LD compaction will not strip these properties, but without the @vocab: _: declaration, they would be stripped. This may be sufficient for experimental or implementation-specific terms that are not expected to be used by anyone else, but it is bad practice for extensibility; the use of the blank namespace for properties is obsolete and may be removed in a future version of JSON-LD.
Compact IRI prefixes can have multiple terms map to the same prefix, depending on which context the producer uses for compaction. For example, say we have a prefix for http://example.com/. You may encounter some documents with example:term, some documents with ex:term, some documents with http://example.com/term, and so on. LD-aware consumers can \"simply\" apply JSON-LD expansion to make all terms unambiguous, and then apply JSON-LD compaction against their local preferred context. LD-unaware consumers instead have to deal with unbounded possible equivalent terms, and will either have to add support for them on a case-by-case basis, or reinvent and reimplement JSON-LD expansion. This issue can be ameliorated by taking care to reuse existing conventional prefixes. An example of this is the [RDFa-Context] \"initial context\".
Since JSON-LD expanded form is unambiguous, it may be a good idea to use it wherever possible. This slightly reduces human readability due to the additional verbosity, but it results in exactly one possible representation of your extension data. LD-unaware consumers will possibly have to learn the structure of JSON-LD expanded form. LD-aware consumers can \"simply\" re-compact the document against any additional contexts they understand.
For example, consider the current use of \"profile fields\" prior to [FEP-fb2a] \"Actor metadata\". Ignoring that Mastodon currently uses sc as a term prefix for an incorrect definition, such a term prefix would be unnecessary if partially-uncompacted JSON-LD was used:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://example.com/~alyssa\",\n \"type\": \"Person\",\n \"name\": \"Alyssa P. Hacker\",\n \"attachment\": [\n {\n \"type\": \"http://schema.org/PropertyValue\",\n \"http://schema.org/name\": \"Pronouns\",\n \"http://schema.org/value\": \"she/her\"\n }\n ]\n}\n In general, try to consider whether your target consumer is expected to understand the context you are declaring. For ActivityStreams-specific consumers, the [AS2-Context] is a requirement, and so it can be generally depended upon. Some specifications (such as [WebAnnotations]) may similarly require their own context to be declared, whereas some specifications may not require this. In general, it may be better to not require a context and to use only the partially-compacted form; this is because multiple context declarations makes it possible for some contexts to conflict, and the most recently-declared context will win out, leading to potentially undefined behavior. This behavior can be avoided by being more judicious about your context declarations and your choice of context documents to compact against.
"},{"location":"fep/e229/#declare-the-activitystreams-context-last-if-compacting-against-additional-contexts","title":"Declare the ActivityStreams context last, if compacting against additional contexts","text":"Because [AP] and [AS2-Core] mandate compaction against the [AS2-Context] while also mandating that terms cannot be overridden, it is best to have the [AS2-Context] be the most-recently-declared context. For example:
{\n \"@context\": [,\n \"https://schema.org\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n // ...\n}\n"},{"location":"fep/e229/#how-to-define-an-extension","title":"How to define an extension","text":"LD-unaware producers need to be aware of at least some of the ways that JSON-LD works; otherwise, their extensions will go into a blank namespace and may be stripped in future versions of JSON-LD. See the recommendations for LD-unaware producers above.
"},{"location":"fep/e229/#extension-property","title":"Extension property","text":"Extension properties will primarily be one of two kinds:
@value.@id.For LD-unaware producers, it is enough to produce JSON of the following form:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"http://example.com/valueProperty\": \"some string or number or boolean\",\n \"http://example.com/idProperty\": {\n \"@id\": \"https://example.com/some-resource\"\n }\n}\n For LD-aware producers, first note that compacting against any additional context will make parsing harder for LD-unaware consumers, as described in above guidance for LD-aware producers -- any additional context you declare must be shared by the consumer, and the only guaranteed context is the ActivityStreams context. Still, for the benefit of LD-aware consumers, you SHOULD at least make a context document available for download out-of-band. One mechanism for doing so is described in [FEP-888d].
The previous example can be expressed as follows when you compact against additional context:
{\n \"@context\": [\n {\n \"valueProperty\": \"http://example.com/valueProperty\",\n \"idProperty\": {\n \"@id\": \"http://example.com/idProperty\",\n \"@type\": \"@id\"\n }\n },\n \"https://www.w3.org/ns/activitystreams\"\n ]\n}\n More complete guidance on term definitions is available within [JSONLD11-TERMS].
"},{"location":"fep/e229/#extension-type","title":"Extension type","text":"Extension types follow similar logic to extension properties with literal values.
For LD-unaware producers, it is enough to use a full IRI as an extension type, although note that some vocabularies have been declared as term prefixes as part of the [AS2-Context], and thus MUST be serialized as compact IRIs using the given prefix. For example, vcard is declared by the [AS2-Context], but schema is not. Thus:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": [\"Person\", \"http://schema.org/Person\", \"vcard:Individual\"]\n}\n LD-aware producers may wish to declare additional context, keeping in mind above guidance for LD-aware producers:
{\n \"@context\": [,\n \"https://schema.org\",\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": [\"Person\", \"schema:Person\", \"vcard:Individual\"]\n}\n Alternatively, to avoid importing the entire Schema.org context:
{\n \"@context\": [\n {\n \"schema\": \"http://schema.org/\"\n },\n \"https://www.w3.org/ns/activitystreams\"\n ],\n \"type\": [\"Person\", \"schema:Person\", \"vcard:Individual\"]\n}\n"},{"location":"fep/e229/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/e232/","title":"FEP-e232: Object Links","text":"Authors Status Date received Date final Tracking issue Discussions Repository silverpill @silverpill@mitra.socialFINAL 2022-08-01 2023-12-03 #14 Discussions codeberg"},{"location":"fep/e232/#summary","title":"Summary","text":"This document proposes a way to represent text-based links to ActivityPub objects which are similar to mentions. One example of such link is inline quote within the value of the content property, but this proposal is not limited to any particular use case.
The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/e232/#object-links","title":"Object links","text":"It is expected that software will allow users to define object links using some kind of microsyntax, similar to @mention and #hashtag microsyntaxes. The exact way of defining object links may vary depending on the use case and is out of scope of this document.
If an object's name, summary, or content has qualified links to other objects, that object SHOULD have the tag property, where each object link is represented as a Link object, as suggested by Activity Vocabulary. The properties of this Link object are:
type (REQUIRED): the type MUST be Link or a subtype.mediaType (REQUIRED): the media type MUST be application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\". This specification only deals with ActivityPub objects but in practice the media type can be different and servers MAY accept object links which do not comply with the requirement. For example, a media type of application/activity+json SHOULD be treated as equivalent.href (REQUIRED): the href property MUST contain the URI of the referenced object.name (OPTIONAL): the name SHOULD match the microsyntax used in object's content.rel (OPTIONAL): if relevant, the rel SHOULD specify how the link is related to the current resource. Using rel can provide additional purpose to object links by signaling specific intended use-cases.(This section is non-normative.)
A link to an issue in a bug tracker:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Note\",\n \"content\": \"The bug was reported in #1374\",\n \"tag\": [\n {\n \"type\": \"Link\",\n \"mediaType\": \"application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"\",\n \"href\": \"https://forge.example/tickets/1374\",\n \"name\": \"#1374\"\n }\n ]\n}\n An inline quote:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Note\",\n \"content\": \"This is a quote:<br>RE: https://server.example/objects/123\",\n \"tag\": [\n {\n \"type\": \"Link\",\n \"mediaType\": \"application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"\",\n \"href\": \"https://server.example/objects/123\",\n \"name\": \"RE: https://server.example/objects/123\"\n }\n ]\n}\n Note that the content includes the RE: <url> microsyntax but consuming implementations are not required to parse that in order to make the appropriate associations.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/e3e9/","title":"Index","text":"hide: - navigation description: \" \"All problems in computer science can be solved by another level of indirection\" (the \"fundamental theorem of software engineering\") -- Attributed to Butler Lampson (src) This FEP introduces an ID scheme for ActivityPub objects and collections that has the following properties IDs remains stable across domain migrations. That is, allows the controller of the objects to change object hosting providers without changing the object IDs. IDs are regular HTTP(S) URLs that are resolvable via an HTTP GET request (provided the client allows following 302 redirects). The proposed mechanism identifies objects by adding query parameters to existing Actor profile URLs. ActivityPub clients wishing to fetch the objects make an HTTP GET request to this URL, as usual, carrying whatever authentication mechanism is required currently, and then follow the HTTP 302 status code redirect in the response to the current storage location of the object. Example Actor-Relative URL https //alice-personal-site.example/actor?service=storage&relativeRef=/AP/objects/567 An AP client, encountering an Object ID with this URL makes an HTTP GET request just as it would with any other Object ID http GET /actor?service=storage&relativeRef=/AP/objects/567 HTTP/1.1 Host alice-personal-site.example The server responds with a 302 redirect (which all HTTP clients are able to automatically follow) pointing to the current storage location of the object. For example http HTTP/1.1 302 Found Location https //storage-provider.example/users/1234/AP/objects/567 This redirection mechanism is enabled in all existing HTTP clients by default (see https //developer.mozilla.org/en-US/docs/Web/API/Request/redirect), and requires no additional re-tooling of ActivityPub client code.\"
"},{"location":"fep/e3e9/#fep-e3e9-actor-relative-urls","title":"FEP-e3e9: Actor-Relative URLs","text":"Authors Status Date received Tracking issue Discussions Repository Dmitri Zagidulin @dmitri@social.coop, bumblefudge bumblefudge@learningproof.xyzDRAFT 2024-06-29 #346 - codeberg"},{"location":"fep/e3e9/#summary","title":"Summary","text":"\"All problems in computer science can be solved by another level of indirection\" (the \"fundamental theorem of software engineering\")
-- Attributed to: Butler Lampson (src)
This FEP introduces an ID scheme for ActivityPub objects and collections that has the following properties:
GET request (provided the client allows following 302 redirects).The proposed mechanism identifies objects by adding query parameters to existing Actor profile URLs. ActivityPub clients wishing to fetch the objects make an HTTP GET request to this URL, as usual, carrying whatever authentication mechanism is required currently, and then follow the HTTP 302 status code redirect in the response to the current storage location of the object.
Example Actor-Relative URL:
https://alice-personal-site.example/actor?service=storage&relativeRef=/AP/objects/567
An AP client, encountering an Object ID with this URL makes an HTTP GET request just as it would with any other Object ID:
GET /actor?service=storage&relativeRef=/AP/objects/567 HTTP/1.1\nHost: alice-personal-site.example\n The server responds with a 302 redirect (which all HTTP clients are able to automatically follow) pointing to the current storage location of the object. For example:
HTTP/1.1 302 Found\nLocation: https://storage-provider.example/users/1234/AP/objects/567\n This redirection mechanism is enabled in all existing HTTP clients by default (see https://developer.mozilla.org/en-US/docs/Web/API/Request/redirect), and requires no additional re-tooling of ActivityPub client code.
"},{"location":"fep/e3e9/#actor-relative-urls-for-objects-and-collections","title":"Actor-Relative URLs for Objects and Collections","text":"On the Client side, the main change required is in the author/controller validation procedure (since retrieving the objects at Actor-Relative URLs requires no additional change beyond ensuring that following HTTP redirects is not disabled).
On the Server side (specifically, the server hosting the Actor profile), two changes are required:
service section to the Actor profile, which is required for author/controller validation.302 redirect responses when an Actor profile request is made that has the required query parameters (service and relativeRef params).In addition:
Given the following example Actor profile:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/did/v1\"\n ],\n \"service\": [{\n \"id\": \"https://alice-personal-site.example/actor#storage\",\n \"serviceEndpoint\": \"https://storage-provider.example\"\n }],\n // Rest of the Actor profile goes here\n}\n When fetching an ActivityPub Object or Collection identified by an Actor-Relative URL (that is, when the Object or Collection ID contains the URL query parameters service and relativeRef), a client MUST validate that the server hosting the Object is authorized by the Actor profile:
GET request on the Object or Collection, as usual, including any currently required authorization headers.GET request MUST be able to support HTTP redirection. For example, if using the WHATWG fetch API, the request's redirect property cannot be set to error.Location header of the 302 response (this behavior is the default in most HTTP clients).Location header of the redirect response; for example, if using the WHATWG fetch API, this is the last URL in the response's URL list, retrievable by accessing response.url.actor or attributedTo property).The Client extracts the value of the authorized storage endpoint from the profile:
a. The Client checks to see if the Actor profile contains the service property. b. If the service property is found, the Client searches through the array of service endpoints until it finds a service endpoint with the relative id ending in #storage (note: this is what the service=storage query parameter refers to, in the Actor-Relative URL). The Client extracts the serviceEndpoint property of this service description object. This is the authorized storage endpoint. c. If no authorized storage endpoint is specified in the Actor profile (that is, if the Actor profile does not contain the service property, or if the service property is null or an empty array, or if the service array does not contain a service endpoint object with a relative id that ends in #storage, or if that service endpoint does not contain a serviceEndpoint property containing a URL), the Client SHOULD indicate to the user that the provenance of this Object cannot be determined, or that the storage location of the Object has not been authorized by the profile of the claimed author/controller.
The Client MUST validate that the current URL of the object is authorized by the Actor's profile by checking that:
a. The Object's currentURL starts with the value of the authorized storage endpoint. b. The Object's currentURL ends with the value of the relativeRef query parameter. c. For example, in JS pseudocode, using string concatenation: response.url === (authorizedStorageEndpoint + query.relativeRef) d. If these checks fail (if the current URL of the object is not equal to the string concatenation of the authorized storage endpoint and the relativeRef query parameter), the Client SHOULD indicate to the user that the provenance of this Object cannot be determined, or that the storage location of the Object has not been authorized by the profile of the claimed author/controller.
This validation procedure establishes a two-way link: from the Object to its author/controller Actor profile (via the Object's actor or attributedTo property), and from the Actor profile to the authorized storage service provider, at whose domain the Object is currently stored.
An ActivityPub client conforming to this FEP:
GET mechanism that it currently does.service and relativeRef query parameters.302 redirect in the response.On the server side (specifically, the server hosting the Actor profile), an ActivityPub server conforming to this FEP:
https://alice-personal-site.example/actor), examine the HTTP QUERY parameters. If the service and relativeRef query parameters are present in the request, treat this as an Actor-Relative URL Request (by following the steps below).Examine the Actor profile object for this request. If the profile does not contain a valid serviceEndpoint that corresponds to the service query parameter, the server MUST return a 422 Unprocessable Entity HTTP status code error. To determine whether the profile contains a valid service endpoint:
If the Actor profile does not contain a top level service property: INVALID
service property, but its value is null or []: INVALIDSearch through the array of service endpoints (the value of the service) property, until you find a service object with the id that ends in <actor profile url>#<contents of the 'service' query param>. See sample Actor profile and request below. If no valid service endpoint is found: INVALID
Assuming that a matching service endpoint is found, compose a current location URL from the serviceEndpoint contained in the profile concatenated with the contents of the relativeRef query parameter (see below for example).
Return a 302 Found HTTP status code response, and set the Location response header to the value of the current location URL composed in the previous step. Note: Servers SHOULD NOT return a 301 status response (a 301 response implies a permanent relocation, and the whole point of this FEP is that Actor-Relative URLs are changeable at any point). Similarly, servers SHOULD not return a 303 See Other status response.
Example request URL:
GET https://alice-personal-site.example/actor?service=storage&relativeRef=/AP/objects/567\n The query parameters would be parsed on the server side as something similar to:
{ \"service\": \"storage\", \"relativeRef\": \"/AP/objects/567\" }\n Example Actor profile at that URL:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/did/v1\"\n ],\n \"service\": [{\n \"id\": \"https://alice-personal-site.example/actor#storage\",\n \"serviceEndpoint\": \"https://storage-provider.example\"\n }],\n // Rest of the Actor profile goes here\n}\n Example current location URL (from concatenating the serviceEndpoint value with the relativeRef query parameter): https://storage-provider.example/AP/objects/567
Example response from the server:
HTTP/1.1 302 Found\nLocation: https://storage-provider.example/AP/objects/567\n"},{"location":"fep/e3e9/#object-storage-migration-using-actor-relative-urls","title":"Object Storage Migration Using Actor-Relative URLs","text":"Actor-Relative URLs can be used as an option for portable Object and Collection IDs that remain unchanged even through migrating to a different object hosting provider (as long as the Actor ID remains constant).
"},{"location":"fep/e3e9/#example-storage-provider-migration","title":"Example Storage Provider Migration","text":"Before migration, Alice uses the https://old-storage-provider.example as a storage provider for her AP objects. She makes sure https://old-storage-provider.example is specified as a service endpoint in her Actor profile.
GET https://alice-personal-site.example/actor
returns
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/did/v1\"\n ],\n \"id\": \"https://alice-personal-site.example/actor\",\n \"type\": \"Person\",\n \"service\": [{\n \"id\": \"https://alice-personal-site.example/actor#storage\",\n \"serviceEndpoint\": \"https://old-storage-provider.example\"\n }],\n \"assertionMethod\": { /* \u2026 */ },\n // All the other profile properties \u2026\n}\n Alice then creates a Note and stores it with the storage provider (making sure to add an Object Identity Proof). Example request:
POST /AP/objects/\nHost: old-storage-provider.example\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Note\",\n \"content\": \"This is a note\",\n \"attributedTo\": \"https://alice-personal-site.example/actor\",\n \"id\": \"https://alice-personal-site.example/actor?service=storage&relativeRef=/AP/objects/567\"\n}\n returns
HTTP 201 Created\nLocation: https://old-storage-provider.example/AP/objects/567\n Note that this created Object can now be fetched at TWO different URLs:
https://old-storage-provider.example/AP/objects/567https://alice-personal-site.example/actor?service=storage&relativeRef=/AP/objects/567When it comes time to migrate to a different service provider, the new one being located at https://brand-new-storage.example, Alice performs the following steps.
She updates her Actor profile service endpoint, to point to the new provider, so that it looks like this:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://www.w3.org/ns/did/v1\"\n ],\n \"id\": \"https://alice-personal-site.example/actor\",\n \"type\": \"Person\",\n \"service\": [{\n \"id\": \"https://alice-personal-site.example/actor#storage\",\n \"serviceEndpoint\": \"https://brand-new-storage.example\"\n }],\n \"assertionMethod\": { /* \u2026 */ },\n // All the other profile properties \u2026\n}\n Note that the serviceEndpoint is the only property in the Actor profile that has to change during migration.
Alice then transfers her Object to the new provider (for this example, she'll be transferring the object individually, though in future FEPs, we expect specification of APIs to transfer all of the objects in one's storage):
POST /AP/objects/\nHost: brand-new-storage.example\n\n{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Note\",\n \"content\": \"This is a note\",\n \"attributedTo\": \"https://alice-personal-site.example/actor\",\n \"id\": \"https://alice-personal-site.example/actor?service=storage&relativeRef=/AP/objects/567\"\n}\n returns:
HTTP 201 Created\nLocation: https://brand-new-storage.example/AP/objects/567\n Notice that the object being stored at the new provider is byte-for-byte identical to the object hosted at the old provider; its indirect id and contents do not change.
Throughout this service provider migration, the external indirect id of the object does not change, for the purposes of all other AP mechanisms such as Inbox delivery, Likes and Reposts, and so on.
FEP-8b32: Object Integrity Proofs
Christine Lemmer Webber, Jessica Tallon, [ActivityPub][AP], 2018
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/e965/","title":"Index","text":"hide: - navigation description: \"This FEP normatively specifies exactly one narrow step in almost all the migration user-stories defined in FEP-73cd User Migration Stories the updates to an Actor object made after a migration and/or deactivation event, and the Move activity which a source server propagates to inform followers of said Actor object update Our proposal clarifies semantics and behavior of the earlier FEP-7628 on which it strictly relies. It also proposes a simple, additive approach to use the above to express \"deactivated\" Actors by \"tombstoning\" their Actor objects, i.e. adding \"Tombstone\" to their type array (already afforded by the Activity Streams vocabulary). It also accomodates migrations to new forms of Actor object, such as \"Nomadic\"-style Portable Actors as described in FEP-ef61 Portable Objects and \"Independently-hosted\" Actor objects as described in FEP-7952, both for conforming and non-conforming consumers. As such, fully implementing all optional features of this proposal would require implementing FEP-521a Representing actor's public keys, which adds terms to the Actor object for publishing a verification method to verify assertions about the Actor independently of domain.\"
"},{"location":"fep/e965/#fep-e965-move-activity-for-migrations-and-announce-activity-for-tombstone-events","title":"FEP-e965: Move Activity for Migrations and Announce Activity for Tombstone Events","text":"Authors Status Date received Tracking issue Discussions Repository bumblefudge bumblefudge@learningproof.xyz, bengo @bengo@social.coopDRAFT 2024-07-05 #352 Discussions codeberg"},{"location":"fep/e965/#summary","title":"Summary","text":"This FEP normatively specifies exactly one narrow step in almost all the migration user-stories defined in FEP-73cd: User Migration Stories:
Our proposal clarifies semantics and behavior of the earlier FEP-7628 on which it strictly relies. It also proposes a simple, additive approach to use the above to express \"deactivated\" Actors by \"tombstoning\" their Actor objects, i.e. adding \"Tombstone\" to their type array (already afforded by the Activity Streams vocabulary). It also accomodates migrations to new forms of Actor object, such as \"Nomadic\"-style Portable Actors as described in FEP-ef61: Portable Objects and \"Independently-hosted\" Actor objects as described in FEP-7952, both for conforming and non-conforming consumers. As such, fully implementing all optional features of this proposal would require implementing FEP-521a: Representing actor's public keys, which adds terms to the Actor object for publishing a verification method to verify assertions about the Actor independently of domain.
Migration is currently supported in a somewhat ad hoc and piecemeal way, without harmonized expectations for how to update, announce, or interpret an Actor object after a migration, or after a deactiviation. Deactivation is sometimes referred to as a \"tombstone\" event, both in general usage in distributed systems and in the Activity Streams sense of the Tombstone object type. Adding the \"Tombstone\" member to the type array of an Actor object marks it as deactivated, as is already possible but more commonly implemented for deleted content or Activities than for Actors. We did not do a review of existing codebases, and the only public prior art that we're aware of is the retrospecification of current practice in FEP-7628: Move Actor.
Beyond passively leaving a Tombstone hint for future queries, there have been no public proposals to our knowledge specifying how to actively express a given Actor's controller expressing an \"intent [for that Actor] to be forgotten\" to other servers where interactions with it may be stored, much less how to document that intent for legal purposes (which is explicitly out of scope here).
MUST, MAY, and SHOULD used in the RFC-2119 sense where they appear in CAPITAL LETTERS. Similarly, the references to \"valid\" URIs throughout should be interpreted as conforming to both RFC-3987 and the [Activity Streams guidance on URI usage][Activity Streams URIs].
Implementations SHOULD signal their support for this specification by including \"https://w3id.org/fep/7628\" in the @context array of their Actors, as this will clearly signal that the ABSENCE of a movedTo or copiedTo property indicates a currently-active Actor.
Implementations MAY prove support for this specification by publishing a Conformance Report referencing the tests run. A specification for possible tests is provided in fep-e965-test-case.
"},{"location":"fep/e965/#actor-object-migration-and-deactivation-syntax","title":"Actor Object Migration and Deactivation Syntax","text":"In the section, \"Move Activity\" of FEP-7628, two variations of the Mastodon-style Move Activity are defined, as well as semantics for the movedTo and copiedTo properties that MUST be applied to the Actor object on the source server of the activity:
If previous primary actor is deactivated after migration, it MUST have movedTo property containing the ID of the new primary actor. [...] If previous primary actor is not deactivated, copiedTo property MUST be used.
We add a few more explicit requirements:
movedTo MUST be a string or an array containing 1 string.copiedTo MUST be a string or an array of strings.movedTo and copiedTo MUST NOT be present in the same Actor object.Many other current and future process and Activities could also be using the same semantics, including new \"styles\" or \"profiles\" of the many possible Actor objects allowed by the [ActivityPub] specification. These include Actors that do not change id after migrating, whether they conform to the Nomadic Actor extension, or to the separately-hosted Actor extension. If an account is moving to one of these configurations, the movedTo or copiedTo value will be the id and location of an ap:// URL, or to a URL controlled by the Actor object's data subject, respectively.
If the Actor object before the deactivation event included a public key for signing Activities expressed according to Client-Signing, and the same public key will NOT be published at the destination server for verifying post-migration Activities, then the source server MAY add an expires key and current-timestamp value to the key's assertionMethod object as described in section #2.3.1: Verification Methods of the W3C Data Integrity specification (to which FEP-521a normatively refers). Any consumer fetching this assertionMethod object for the purposes of verifying signatures according to the Data Integrity algorithm will thus invalidate signatures newer than the deactivation of that key.
If an account has been deleted intentionally and consuming implementations are expected to recognize this, regardless of whether or not a movedTo value has been set, a server MUST include the string \"Tombstone\" in the type array of the deactivated or moved Actor object. Whether any other types are present is out of scope of this specification, to minimize side effects or complications for implementers.
If an account whose Actor object containing a valid copiedTo value has been deleted intentionally, this MUST be removed before adding Tombstone to the Actor's type. A server performing this removal MAY move one valid URI from copiedTo to movedTo to aid discovery.
If a user account is being deactivated but the source server wants to enable a future migration to be authenticated cryptographically, it MAY add to the Actor object a public key authenticated to the account (if not already present), as per to FEP-521a.
An Actor object set to Tombstone SHOULD also set a top-level as:deleted property containing a current XSD dateTime as a courtesy to consumers.
After these changes have been made to the Actor object on the source server, it should be announced according to the type of changes made. If movedTo or copiedTo have been changed, then a FEP-73cd-compatible Move activity with the Actor as its object SHOULD be sent out, at least to all of that Actor's Followers. If the Actor is being retyped to Tombstone, an Announce activity with the Actor as object should be sent out, at least to all of that Actor's followers.
If a user account is being deactivated but the source server wants to enable a future migration to be authenticated cryptographically, it is RECOMMENDED that the Announce activity be signed as per FEP-8b32. In this way, a later Move activity signed by the same key could, at least theoretically, be accepted from a new server which authenticates it by FEP-8b32.
"},{"location":"fep/e965/#interpreting-a-migrated-or-deactivated-actor-object","title":"Interpreting a Migrated or Deactivated Actor Object","text":"In the section, \"movedTo and copiedTo properties\" of FEP-7628, the following general rule for all Actor objects is proposed:
Publishers SHOULD NOT deliver activities to actor's inbox if movedTo property is present.
We add the following behavioral expectations:
movedTo property to find out if it contains an inbox property.movedTo value is set and one or more copiedTo values are set, publishers MAY resolve a copiedTo value to retrieve an inbox value and similarly process it.copiedTo inbox and allowance by policy, delivery MAY attempt delivery to both Actor inbox and copiedTo inbox(es).movedTo value has been set to a valid URI, but type does not include \"Tombstone\", consuming implementations SHOULD treat it as a deactivated actor per Postel's Law.There are caveats to interpreting these values if the movedTo or copiedTo properties contain unconventional URLs, such as those generated by an implementation extended by the above-mentioned FEPs:
movedTo or copiedTo value is a valid URL beginning with the prefix ap:// and the @context value includes the relevant extension properties, the destination server of the migration is likely implementing FEP-ef61 and may require custom resolution logic to return an Actor object.movedTo or copiedTo value contains an actor-relative URL of the type defined in FEP-7952, it should resolve as usual if the server is live, as long as the querying implementation allows for the HTTP redirect and has no policy against (or hardcoded assumptions incompatible with) inbox values on different domains than id values for a given Actor.movedTo or a non-empty copiedTo value in turn, this should in turn be dereferenced, barring domain-based policies to the contrary.movedTo values be displayed to end-users as corrupted or incomplete moves, rather than as deactivated accounts.Servers receiving a Move or an Announce activity with an Actor as its object SHOULD NOT increment a shares collection. If a receiving server persists redirects or aliases to more smoothly remain aware of migrating or multi-homed users, or for other reasons, it MAY resolve the new Actor object and perform the above-described checks and MAY record said Actor update.
FEP-ef61: Portable Objects
Christine Lemmer Webber, Jessica Tallon, ActivityPub, 2018
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/e965/fep-e965-test-case/","title":"Actor Object Migration and Deactivation Syntax","text":""},{"location":"fep/e965/fep-e965-test-case/#background","title":"Background","text":"This proposal extends and combines prior FEPs to define syntax and parsing rules for Actor objects which unambiguously express exactly one of the three following states:
This is a Test Case testing conformance with the two properties of an Actor object specified in FEP-e965.
"},{"location":"fep/e965/fep-e965-test-case/#test-subject","title":"Test Subject","text":"The subject of this test is any data claiming to conform to the specification of an ActivityPub Actor Object and to the FEP-e965 extension thereof.
This test is not directly applicable to an ActivityPub Server. An ActivityPub Server serves 0 or more Actor Objects. An ActivityPub Server for a big community might serve hundreds of ActivityPub Actor Objects. An ActivityPub Server for a single human may serve only that person's ActivityPub Actor Object.
This test applies to Actor Objects, but not all Actor Objects are addressable by an HTTPS URL. The URI that addresses an Actor Object is not the same as the Actor Object. A given URL may resolve to different Actor Objects in different contexts, and a given Actor Object may not be universally addressable across context by any one URL.
"},{"location":"fep/e965/fep-e965-test-case/#inputs","title":"Inputs","text":"This test requires the following inputs:
actor - the actor object under testedinapplicable.@context array should include both terms defined by FEP-7628 to signal support for this FEPinapplicable.This test applies directly to the actor input.
actor is not a JSON object, the outcome MUST be inapplicable.actor MUST have a @context property whose value is an Array containing the string https://w3id.org/fep/7628. If it does not, the outcome MUST be inapplicable.actor is the only test targetmovedTo - MUST be a URI OR an empty string, if presentcopiedTo - MUST be a URI, if presentmovedTo and copiedTo MUST NOT both be presentactor JSON's @context array SHOULD include \"https://w3id.org/fep/7628\" to signal conformanceFor the purposes of determining whether the active status and migration history of a given Actor can be tested by this test case:
@context property@context array includes the URL \"https://w3id.org/fep/7628\"A warning should be returned if this value is not present.
"},{"location":"fep/e965/fep-e965-test-case/#2-property-value-expectations","title":"2. Property value expectations","text":"For the purposes of determining the in/active status and migration history of a the target Actor:
movedTo and copiedTo MUST NOT both be presentmovedTo can be a valid URI OR not presentcopiedTo can be a valid URI or an array containing one or more valid URIs OR not presentmovedTo and copiedTo MUST NOT both be presentFAILEDmovedTo is present,PASSEDFAILEDcopiedTo is present,type MUST not include \"Tombstone\"FAILED; log (\"Cannot be tombstoned if copiedTo is set\")PASSEDFAILED; log (\"invalid values in copiedTo\")PASSEDThese are test cases for this test case, and can be used to verify that an implementation of this test case specification will be consistent with other implementations.
"},{"location":"fep/e965/fep-e965-test-case/#missing-context-values","title":"Missing@context values","text":"input
actor:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\"\n}\n test result
inapplicablemovedTo and copiedTo present","text":"input
actor:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"movedTo\": \"https://otherexample.com/newname\",\n \"copiedTo\": \"https://otherexample.com/thirdname\"\n}\n test return
FAILED, log (movedTo and copiedTo MUST NOT both be present)movedTo set to array","text":"input
actor:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"movedTo\": [\n \"https://example2.com/id\",\n \"https://example3.com/id\"\n ],\n}\n test return
FAILED, log (movedTo MUST be a functional property)copiedTo contains invalid URI","text":"input
actor:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"copiedTo\": [\n \"https://example2.com/id\",\n \"Tombstone\"\n ],\n}\n test return
FAILED, log (movedTo MUST be a functional property)movedTo set to invalid URI #1","text":"input
actor:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"movedTo\": \"Tombstone\"\n}\n test return
FAILED, log (movedTo MUST be a URI)movedTo set to invalid URI #2","text":"input
actor:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"movedTo\": \"\"\n}\n test return
FAILED, log (movedTo MUST be a URI)input
actor:{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": [\"Person\",\"Tombstone\"],\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\"\n}\n test return
PASSEDinput
actor:{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": [\"Person\",\"Tombstone\"],\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"movedTo\": \"https://actorname.otherexample.com\"\n}\n test return
PASSEDThis test vector does NOT conform to [FEP0-f2a] but DOES conform to the older FEP-7628. It is included to assist in testing the consumption of legacy migrated actors.
input
actor:{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": [\"Person\"],\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"movedTo\": \"https://actorname.otherexample.com\"\n}\n test return
FAILEDinput
actor:{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"copiedTo\": \"https://personalarchive.otherexample.com\"\n}\n test return
PASSEDcopiedTo set to array)","text":"input
actor:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/fep/7628\"\n ],\n \"type\": \"Person\",\n \"inbox\": \"https://example.com/inbox\",\n \"outbox\": \"https://example.com/outbox\",\n \"copiedTo\": [\n \"https://example2.com/id\",\n \"https://example3.com/id\"\n ],\n}\n test return
PASSED, log (copiedTo contains multiple valid URIs)outcome","text":"An outcome is a conclusion that comes from evaluating a test on a test subject. An outcome can be one of the three following types:
inapplicable: No part of the test subject matches the applicabilitypassed: A test target meets all expectationsfailed: A test target does not meet all expectationsassertionMethod has outcome passed, requirement is satisfiedassertionMethod has outcome failed, requirement is not satisfiedassertionMethod has outcome inapplicable, further testing is needed to determine whether this requirement is satisfiedDRAFT 2024-07-25 #386 Discussions codeberg"},{"location":"fep/eb22/#summary","title":"Summary","text":"Servers can advertise what features of the API they support, such as creating a poll or boosting a post. Clients can recognize if a server doesn't support a feature and hide it from the UI.
"},{"location":"fep/eb22/#background","title":"Background","text":"As the fediverse grows, we'll naturally see a wide range of servers and clients, and not all of them will exactly match the features that popular clients like Mastodon support. To support a diverse mix of clients with different user experiences, clients and servers should communicate about which features should be shown in the client UI.
Some services might not include public likes or polls, for example. Some might not include boosting. It would be confusing for a boost icon to show an error message when clicked if that feature was not available. Instead, the client should detect whether boosting is available, and if not simply hide the icon when connected to that server.
"},{"location":"fep/eb22/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this specification are to be interpreted as described in RFC 2119.
"},{"location":"fep/eb22/#proposal","title":"Proposal","text":"Document in NodeInfo the ActivityStreams types and properties that correspond to features the server supports. This information can be used by both ActivityPub clients as well as Mastodon API clients. We use ActivityStreams types because they are already part of a fediverse standard, rather than inventing new keys to describe existing features.
Add a new top-level field types to NodeInfo with three fields, all of which are optional:
activities: The types of activities the server broadly supports.objects: The types of objects that the server supports in the relevant activities.properties: For each activity or object, what properties are allowed in that object.If no types are present in NodeInfo, a client should assume a server supports all types, just as most clients do today.
If no objects are present for an activity, a client should assume a server supports all common objects. If a limited number of objects are present, a client should assume a server only supports those objects.
If no properties are present for an object, a client should assume a server supports all common properties. If a limited number of properties are present, a client should assume a server only supports those properties.
"},{"location":"fep/eb22/#examples","title":"Examples","text":"The following examples are simplified versions of what most servers should support. Most popular servers will support more activity and object types than this, but these examples keep the list short for readability. Other common activities include Follow, Undo, Accept, Block, and others as listed in the Activity Vocabulary specification.
Review)","text":"This example adds Review, an object used by BookWyrm for book reviews. Because only properties for Review are specified, a client can assume that all common objects and properties for the other activities are supported.
{\n \"types\": {\n \"activities\": [\n \"Create\",\n \"Like\",\n \"Announce\",\n \"Question\",\n \"Move\",\n \"Follow\"\n ],\n \"objects\": [\n \"Note\",\n \"Article\",\n \"Image\",\n \"Review\"\n ],\n \"properties\": {\n \"Review\": [ \"title\", \"body\", \"rating\", \"inReplyToBook\" ]\n }\n }\n}\n"},{"location":"fep/eb22/#supporting-common-social-features-but-not-supporting-polls-question","title":"Supporting common social features, but not supporting polls (Question)","text":"This example leaves off the Question type and the oneOf property used for polls.
{\n \"types\": {\n \"activities\": [\n \"Create\",\n \"Like\",\n \"Announce\"\n ],\n \"objects\": [\n \"Note\",\n \"Article\",\n \"Image\"\n ],\n \"properties\": {\n \"Note\": [ \"summary\", \"content\", \"published\", \"inReplyTo\" ],\n \"Article\": [ \"name\", \"content\", \"published\" ]\n }\n }\n}\n"},{"location":"fep/eb22/#supporting-common-social-features-including-polls-but-not-supporting-titled-posts-name","title":"Supporting common social features including polls, but not supporting titled posts (name)","text":"This example includes only the content and published properties for Article, meaning the client should not prompt for a title for long-form articles.
{\n \"types\": {\n \"activities\": [\n \"Create\",\n \"Like\",\n \"Announce\",\n \"Question\",\n ],\n \"objects\": [\n \"Note\",\n \"Article\",\n \"Image\"\n ],\n \"properties\": {\n \"Question\": [ \"oneOf\" ],\n \"Note\": [ \"summary\", \"content\", \"published\", \"inReplyTo\" ],\n \"Article\": [ \"content\", \"published\" ]\n }\n }\n}\n"},{"location":"fep/eb22/#supporting-common-social-features-but-not-supporting-boosts-announce","title":"Supporting common social features, but not supporting boosts (Announce)","text":"This example lists the bare minimum types, leaving off the explicit list of objects and properties. Clients should assume the server supports whatever properties are common for those types, such as Note, Article, and others.
{\n \"types\": {\n \"activities\": [\n \"Create\",\n \"Like\",\n \"Question\",\n \"Move\"\n ]\n }\n}\n"},{"location":"fep/eb22/#supporting-common-social-features-and-account-migration-move","title":"Supporting common social features and account migration (Move)","text":"This example lists object and target fields that are used for account migration with the Move activity.
{\n \"types\": {\n \"activities\": [\n \"Create\",\n \"Like\",\n \"Announce\",\n \"Question\",\n \"Move\"\n ],\n \"properties\": {\n \"Move\": [ \"object\", \"target\" ]\n }\n }\n}\n"},{"location":"fep/eb22/#related-proposals","title":"Related proposals","text":"There have been a few other attempts at addressing how to describe what a server supports:
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/eb48/","title":"FEP-eb48: Hashtags","text":"Authors Status Date received Tracking issue Discussions Repository AvidSeeker avidseeker7@protonmail.comDRAFT 2024-07-16 #373 Discussions codeberg"},{"location":"fep/eb48/#summary","title":"Summary","text":"This proposal introduces a standardized method for identifying and displaying hashtags in posts across the Fediverse. The rules define what constitutes a hashtag and how it should be parsed and displayed, ensuring consistency and predictability across different platforms and clients.
"},{"location":"fep/eb48/#hashtag-rules","title":"Hashtag Rules","text":"A string is considered a hashtag if it meets the following criteria:
# symbol._) but must not include any other special characters, spaces, or punctuation within the hashtag itself.The following examples illustrate how hashtags should be identified and highlighted:
#hashtag#hashtag\"#hashtag\"#hashtag/#hashtag)#hashtag/#hashtag)#hashtag /#hashtag)#hashtag / #hashtag)#hashtag#hashtag#hashtag#hashtag#hashtag#hashtag#hashtag#hashtag#hashtag#hashtag#hashtagCC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/ee3a/","title":"FEP-ee3a: Exif metadata support","text":"Authors Status Date received Tracking issue Discussions Repository Marcin Czachurski mczachurski@icloud.comDRAFT 2026-01-13 #752 Discussions codeberg"},{"location":"fep/ee3a/#summary","title":"Summary","text":"The exchangeable image file format ([Exif]) family combines file formats such as JPEG, TIFF and WAV with structured metadata. [Exif] records camera (e.g., lens data, focal length, exposure time) and audio (e.g., channel count, sampling rate) recording parameters. The standard originally focused on photography but was expanded with version 2.1 to cover sound recordings. This proposal defines a Fediverse-wide mechanism for conveying [Exif] metadata using the [exifData] property from the [Schema.org] vocabulary.
To improve interoperability and promote consistent handling of attachment metadata across [ActivityPub] implementations, this FEP defines a vocabulary and processing rules for exposing Exif metadata associated with image (Image) and audio (Audio) attachments.
"},{"location":"fep/ee3a/#description","title":"Description","text":"In this FEP, Exif metadata is represented as an array of [PropertyValue] items under the exifData property of an Image or Audio object. Each item MUST include an @type of PropertyValue, a name identifying the Exif tag and a value holding the tag's value. All Exif tags defined in the official Exif specification may be represented, but implementations are encouraged to support a recommended subset for interoperability.
Exif metadata can reveal sensitive information such as a user's location, device details or recording environment. Geographic coordinates (latitude and longitude) can expose personal addresses or travel patterns. Producers MUST obtain user consent before including these fields and SHOULD clearly inform users about what information will be published. Consumers SHOULD treat geographic and device metadata as sensitive and avoid displaying it publicly without explicit permission.
"},{"location":"fep/ee3a/#definitions","title":"Definitions","text":""},{"location":"fep/ee3a/#exifdata-property","title":"exifData property","text":"exifData is an optional property attached to media objects of type Image or Audio. When present, its value MUST be an array of objects where each object: - has @type equal to \"PropertyValue\"; - has a name property containing the Exif field name (e.g., \"ExposureTime\", \"SamplesPerSec\"); - has a value property containing the corresponding value.
Implementations MUST ignore unknown properties. Schema.org's definition of exifData permits the value to be a string, but this FEP standardizes on the structured array representation for interoperability.
Property names MUST be strings corresponding to the Exif specification Field name (e.g., \"FNumber\", \"PhotographicSensitivity\", \"FocalLength\").
Implementations MAY expose any Exif tag via exifData when present in the file and permitted by user consent. For interoperability, the following tag names and descriptions are recommended. Fields that apply only to images or only to audio are indicated in the second column.
DateTime image, audio Date and time when the media was created. Exif's DateTime tag uses the format \"YYYY:MM:DD HH:MM:SS\". The time is expressed in the photographer's local time zone. ExposureTime image Exposure time (e.g., \"1/100\" or \"4\"). FNumber image Aperture value expressed as an f-number (e.g., \"f/1.8\"). Flash image Description of flash usage (e.g., \"Flash did not fire.\"). FocalLength image Focal length reported by the camera. FocalLengthIn35mmFilm image 35 mm equivalent focal length. GPSLatitude image Exact latitude of the photo location. GPSLatitudeRef image Indicates whether the latitude of shooting location is north or south latitude. 'N' indicates north latitude, and 'S' is south latitude. GPSLongitude image Exact longitude of the photo location (requires user consent). GPSLongitudeRef image Indicates whether the longitude of shooting location is east or west longitude. 'E' indicates east longitude, and 'W' is west longitude. LensMake image Lens manufacturer. LensModel image Lens model name. Make image, audio Device manufacturer. Model image, audio Device model. PhotographicSensitivity image ISO sensitivity. Software image, audio Editing software or firmware used. SamplesPerSec audio Sampling frequency (e.g., \"44100 Hz\"). AvgBytesPerSec audio Bit depth per sample (e.g., \"16 bit\"). Channels audio Channel configuration (e.g., \"mono\", \"stereo\"). Compression audio Compression scheme (e.g., \"PCM\", \"\u03bc-Law\", \"ADPCM\"). Implementations MAY include additional tags and MUST ignore tags they do not understand. Property names SHOULD be consistent across implementations to facilitate display and filtering.
"},{"location":"fep/ee3a/#examples","title":"Examples","text":""},{"location":"fep/ee3a/#image-example","title":"Image example","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"schema\": \"https://schema.org/\"\n }\n ],\n \"type\": \"Note\",\n \"content\": \"Sunrise photo.\",\n \"attachment\": [{\n \"type\": \"Image\",\n \"url\": \"https://example.org/photos/123.jpg\",\n \"mediaType\": \"image/jpeg\",\n \"exifData\": [\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"DateTime\",\n \"value\": \"2025:03:30 06:30:00\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"ExposureTime\",\n \"value\": \"1/250\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"FNumber\",\n \"value\": \"f/5.6\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"FocalLength\",\n \"value\": \"70 mm\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"LensModel\",\n \"value\": \"Canon EF 70-200mm\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Make\",\n \"value\": \"Canon\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Model\",\n \"value\": \"EOS R5\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"PhotographicSensitivity\",\n \"value\": \"400\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Software\",\n \"value\": \"Darktable\"\n }\n ]\n }]\n}\n"},{"location":"fep/ee3a/#audio-example","title":"Audio example","text":"{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"schema\": \"https://schema.org/\"\n }\n ],\n \"type\": \"Note\",\n \"content\": \"Field recording.\",\n \"attachment\": [{\n \"type\": \"Audio\",\n \"url\": \"https://example.org/audio/field.wav\",\n \"mediaType\": \"audio/wav\",\n \"exifData\": [\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"DateTime\",\n \"value\": \"2025-03-02T14:00:00Z\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"SamplesPerSec\",\n \"value\": \"48000 Hz\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"AvgBytesPerSec\",\n \"value\": \"24 bit\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Channels\",\n \"value\": \"stereo\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Compression\",\n \"value\": \"PCM\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Make\",\n \"value\": \"Sony\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Model\",\n \"value\": \"PCM-D10\"\n },\n {\n \"@type\": \"PropertyValue\",\n \"name\": \"Software\",\n \"value\": \"Audacity\"\n }\n ]\n }]\n}\n"},{"location":"fep/ee3a/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/ef61/","title":"FEP-ef61: Portable Objects","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2023-12-06 #209 Discussions codeberg"},{"location":"fep/ef61/#summary","title":"Summary","text":"Portable ActivityPub objects with server-independent IDs.
"},{"location":"fep/ef61/#motivation","title":"Motivation","text":"Usage of HTTP(S) URIs as identifiers has a major drawback: when the server disappears, everyone who uses it loses their identity and data.
The proposed solution should satisfy the following constraints:
Nomadic identity mechanism makes identity independent from a server and was originally part of the Zot federation protocol.
Streams (2021) made nomadic accounts available via the Nomad protocol, which supported ActivityStreams serialisation.
FEP-c390 (2022) introduced a decentralized identity solution compatible with ActivityPub. It enabled permissionless migration of followers between servers, but didn't provide full data portability.
"},{"location":"fep/ef61/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/ef61/#identifiers","title":"Identifiers","text":"An ActivityPub object can be made portable by using an identifier that is not tied to a single server. This proposal describes a new identifier type that has this property and is compatible with the ActivityPub specification.
"},{"location":"fep/ef61/#ap-uris","title":"'ap' URIs","text":"'ap' URI is constructed according to the RFC-3986 specification, but with a Decentralized Identifier in place of the authority:
ap://did:example:abcdef/path/to/object?name=value#fragment-id\n\\_/ \\________________/ \\____________/ \\________/ \\_________/\n | | | | |\nscheme authority path query fragment\n ap.Warning
An 'ap' URI is not a valid RFC-3986 URI if reserved characters in the authority component are not percent-encoded. Nevertheless, this form is considered canonical.
Note
ActivityPub specification requires identifiers to have an authority \"belonging to that of their originating server\". The authority of 'ap' URI is a DID, which does not belong to any particular server.
Warning
The URI scheme might be changed to ap+ef61 in a future version of this document, because these identifiers are not intended to be used for all ActivityPub objects, but only for portable ones.
Two 'ap' URIs are equivalent when their canonical forms are identical.
To produce a canonical 'ap' URI, the following operations MUST be performed:
Implementers MUST support the did:key method. Other DID methods SHOULD NOT be used, as it might hinder interoperability.
Note
The following additional DID methods are being considered: did:web, did:dns, did:webvh (formerly did:tdw) and did:fedi.
To maintain backward compatibility with existing ActivityPub implementations that rely on an origin-based security model and do not canonicalize IDs before comparison, implementers MUST generate DIDs using the base58-btc alphabet, even though the specification allows both base58-btc and base64url. Using both alphabets in practice could prevent such servers from recognizing that a post whose attributedTo value is https://base64url.example/.well-known/apgateway/did:key:u7QGwDY2Tjn93PVFWWq02piP1NE9_XRlg-c8-jhJiDqKBDw/actor belongs to https://base58.example/.well-known/apgateway/did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/actor.
DID documents SHOULD contain Ed25519 public keys represented as verification methods with Multikey type (as defined in the Controlled Identifiers specification).
Any DID URL capabilities of a DID method MUST be ignored when working with 'ap' URIs.
"},{"location":"fep/ef61/#dereferencing-ap-uris","title":"Dereferencing 'ap' URIs","text":"To dereference an 'ap' URI, the client MUST make HTTP GET request to a gateway endpoint at well-known location /.well-known/apgateway. The ap:// prefix MUST be removed from the URI and the rest of it appended to a gateway URI. The client MUST specify an Accept header with the application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\" media type.
Example of a request to a gateway:
GET https://social.example/.well-known/apgateway/did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/path/to/object\n ActivityPub objects identified by 'ap' URIs can be stored on multiple servers simultaneously.
If object identified by 'ap' URI is stored on the server, it MUST return a response with status 200 OK containing the requested object. The value of a Content-Type header MUST be application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\".
If object identified by 'ap' URI is not stored on the server, it MUST return 404 Not Found.
If object is not public, the server MUST return 404 Not Found unless the request has a HTTP signature and the signer is allowed to view the object.
Note
This document describes web gateways, which use HTTP transport. However, the data model and authentication mechanism are transport-agnostic and other types of gateways could exist.
"},{"location":"fep/ef61/#authentication-and-authorization","title":"Authentication and authorization","text":"Authentication and authorization are performed in accordance with FEP-fe34 origin-based security model, but with two important differences:
The origin of an 'ap' URI is identical to the authority component of its canonical form (i.e. it is a DID without percent encoding).
The origin of a DID URL is identical to its did component.
Actors, activities and objects identified by 'ap' URIs MUST contain FEP-8b32 integrity proofs. Collections identified by 'ap' URIs MAY contain integrity proofs. If collection doesn't contain an integrity proof, another authentication method MUST be used.
The value of verificationMethod property of the proof MUST be a DID URL where the DID matches the authority component of the 'ap' URI.
Note
This document uses terms \"actor\", \"activity\", \"collection\" and \"object\" according to the classification given in FEP-2277.
"},{"location":"fep/ef61/#portable-actors","title":"Portable actors","text":"One DID subject can control multiple actors (which are differentiated by the path component of an 'ap' URI).
An actor object identified by 'ap' URI MUST have a gateways property containing an ordered list of gateways where the latest version of that actor object can be retrieved. Each item in the list MUST be an HTTP(S) URI with empty path, query and fragment components. The list MUST contain at least one item.
Gateways are expected to be the same for all actors under a DID authority and MAY be also specified in the DID document as services.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v1\",\n \"https://w3id.org/fep/ef61\"\n ],\n \"type\": \"Person\",\n \"id\": \"ap://did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/actor\",\n \"inbox\": \"ap://did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/actor/inbox\",\n \"outbox\": \"ap://did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/actor/outbox\",\n \"gateways\": [\n \"https://server1.example\",\n \"https://server2.example\"\n ],\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"created\": \"2023-02-24T23:36:38Z\",\n \"verificationMethod\": \"did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2#z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\"\n }\n}\n"},{"location":"fep/ef61/#location-hints","title":"Location hints","text":"When ActivityPub object containing a reference to another actor is being constructed, implementations SHOULD provide a list of gateways where specified actor object can be retrieved. This list MAY be provided using the gateways query parameter. Each gateway address MUST be URI-endcoded, and if multiple addresses are present they MUST be separated by commas.
Example:
ap://did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/actor?gateways=https%3A%2F%2Fserver1.example,https%3A%2F%2Fserver2.example\n This URI indicates that object can be retrieved from two gateways:
https://server1.examplehttps://server2.exampleImportant
When comparing 'ap' URIs, query parameters are discarded and canonical URIs are used.
"},{"location":"fep/ef61/#inboxes-and-outboxes","title":"Inboxes and outboxes","text":"Portable inboxes and outboxes function as described in the ActivityPub specification. These endpoints are also used to synchronize activities between gateways used by an actor.
Servers specified in the gateways property of an actor object MUST accept POST requests targeting its inbox collection.
Example:
POST https://social.example/.well-known/apgateway/did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/actor/inbox\n Activities delivered to an inbox might be not portable. If the server does not accept deliveries on behalf of an actor, it MUST return 404 Not Found.
Upon receiving an activity in actor's inbox, the server SHOULD forward it to inboxes located on other servers where actor's data is stored. An activity MUST NOT be forwarded from inbox more than once.
Servers specified in the gateways property of an actor object MAY accept POST requests targeting its outbox collection. Such servers MUST implement FEP-ae97.
Activities delivered to an outbox are performed by a portable actor and therefore MUST be portable too. The server MUST verify them as described in section Authentication and authorization and then process them as described in FEP-ae97. Clients MAY deliver activities to multiple outboxes, located on different servers.
Upon receiving an activity in actor's outbox, the server SHOULD forward it to outboxes located on other servers where actor's data is stored. An activity MUST NOT be forwarded from outbox more than once.
"},{"location":"fep/ef61/#collections","title":"Collections","text":"Collections associated with portable actors (such as inbox and outbox collections) MAY not have FEP-8b32 integrity proofs. Consuming implementations MUST NOT process unsecured collections retrieved from servers that are not listed in the gateways array of the actor document.
Example:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n \"https://w3id.org/security/data-integrity/v1\",\n \"https://w3id.org/fep/ef61\"\n ],\n \"type\": \"Note\",\n \"id\": \"ap://did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/objects/dc505858-08ec-4a80-81dd-e6670fd8c55f\",\n \"attributedTo\": \"ap://did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/actor?gateways=https%3A%2F%2Fserver1.example,https%3A%2F%2Fserver2.example\",\n \"inReplyTo\": \"ap://did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/objects/f66a006b-fe66-4ca6-9a4c-b292e33712ec\",\n \"content\": \"Hello!\",\n \"attachment\": [\n {\n \"type\": \"Image\",\n \"url\": \"hl:zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n\",\n \"mediaType\": \"image/png\",\n \"digestMultibase\": \"zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n\"\n }\n ],\n \"to\": [\n \"ap://did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/actor\"\n ],\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"created\": \"2023-02-24T23:36:38Z\",\n \"verificationMethod\": \"did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2#z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"...\"\n }\n}\n"},{"location":"fep/ef61/#media","title":"Media","text":"Integrity of an external resource is attested with a digest. When a portable object contains a reference to an external resource (such as image), it MUST also contain a digestMultibase property representing the integrity digest of that resource. The digest MUST be computed using the SHA-256 algorithm.
The URI of an external resource SHOULD be a hashlink.
Example of an Image attachment:
{\n \"type\": \"Image\",\n \"url\": \"hl:zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n\",\n \"mediaType\": \"image/png\",\n \"digestMultibase\": \"zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n\"\n}\n After retrieving a resource, the client MUST verify its integrity by computing its digest and comparing the result with the value encoded in digestMultibase property.
Resources attached to portable objects using hashlinks can be stored by gateways. To retrieve a resource from a gateway, the client MUST make an HTTP GET request to the gateway endpoint at well-known location /.well-known/apgateway. The value of a hashlink URI MUST be appended to the gateway base URI.
Example of a request:
GET https://social.example/.well-known/apgateway/hl:zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n\n"},{"location":"fep/ef61/#compatibility","title":"Compatibility","text":""},{"location":"fep/ef61/#identifiers_1","title":"Identifiers","text":"'ap' URIs might not be compatible with existing ActivityPub implementations. To provide backward compatibility, gateway-based HTTP(S) URIs of objects can be used instead of their canonical identifiers:
https://social.example/.well-known/apgateway/did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/path/to/object\n Publishers MUST use the first gateway from actor's gateways list when constructing compatible identifiers. Consuming implementations that support 'ap' URIs MUST remove the part of the URI preceding did: and re-construct the canonical identifier. Objects with the same canonical identifier, but located on different gateways MUST be treated as different instances of the same object.
Publishers MUST NOT add the gateways query parameter to object IDs if compatible identifiers are used.
When HTTP signatures are necessary for communicating with other servers, each gateway that makes requests on behalf of an actor SHOULD use a separate secret key. The corresponding public keys MUST be added to actor document using the assertionMethod property as described in FEP-521a.
WebFinger address of a portable actor can be obtained by the reverse discovery algorithm described in section 2.2 of ActivityPub and WebFinger report, but instead of taking the hostname from the identifier, it MUST be taken from the first gateway in actor's gateways array.
(This section is non-normative.)
"},{"location":"fep/ef61/#discovering-locations","title":"Discovering locations","text":""},{"location":"fep/ef61/#arbitrary-paths","title":"Arbitrary paths","text":"The gateways array can contain HTTP(S) URIs with a path component, thus enabling discovery based on the \"follow your nose\" principle, as opposed to discovery based on a well-known location.
Example of a compatible object ID if the gateway endpoint is https://social.example/ap:
https://social.example/ap/did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2/path/to/object\n"},{"location":"fep/ef61/#alternatives-to-gateways-property","title":"Alternatives to gateways property","text":"This proposal makes use of the gateways property, but the following alternatives are being considered:
gateways property in actor's endpoints mappingaliases and sameAs (containing HTTP(S) URIs of objects)alsoKnownAs (used for account migrations, so the usage of this property may cause issues)url (with alternate relation type)Instead of specifying gateways in actor document, they can be specified in DID document using DID services. This approach is not compatible with generative DID methods such as did:key, which might be necessary for some types of applications.
The proposed approach to referencing media with hashlinks does not support access control: anybody who knows the hash can retrieve the file.
To work around this limitation, a different kind of identifier can be used where digest is combined with the ap:// identifier of its parent document. The gateway will not serve media unless parent document ID is provided, and will check whether request signer has permission to view the document and therefore the attached media.
The following alternatives to gateway-based compatible IDs are being considered:
url property (with canonical relation type, as proposed in FEP-fffd). For pointers to other objects such as inReplyTo property, an embedded object with url property can be used instead of a plain URI.CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/efda/","title":"FEP-efda: Followable objects","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT 2025-02-13 #492 Discussions codeberg"},{"location":"fep/efda/#summary","title":"Summary","text":"ActivityStreams Vocabulary defines a Follow activity, and ActivityPub defines its side effects of manipulating a followers collection, but ActivityPub does not specify a full algorithm for how to follow something. This FEP aims to provide guidance on which objects can be followed:
followers collection present.inbox, then you MAY recurse upwards through attributedTo until you find a resource with an inbox. The maximum recursion depth SHOULD be 1.A Follow activity can then be constructed for that object and delivered to the discovered inbox. Additional requirements for the structure of the Follow activity are out-of-scope.
In order to follow an object, we use a Follow activity. But in order to use this Follow activity, we need to know the following:
1) What can be followed? 2) Where do we send the Follow for that object?
At the time of writing, current software practices within the fediverse enforce a limit on what can be followed. For example, Mastodon currently limits follows to actors that can be mapped to their internal concept of \"accounts\", and these \"accounts\" are in turn limited to being any of the five \"actor types\" described in AS2-Vocab: Person, Group, Organization, Application, Service.
Consequently, other software which does not have such a conceptual limit is forced to declare their actors as one of the five types, or else Mastodon users will not be able to follow their actors. ActivityPub says that there is no specified mapping between \"users\" and \"actors\", but Mastodon's interpretation of \"actors\" as \"users\" de facto disallows following anything but a strict subset of resources.
We may therefore express Mastodon's requirements as follows:
type of any of Person, Group, Organization, Application, Service.inbox where the Follow will be sent.A proposed modified requirement and algorithm are as follows:
followers collection present. (If it does not, then it is unknown whether it is followable.)inbox, then you MAY recurse upwards through attributedTo until you find a resource with an inbox. The maximum recursion depth SHOULD be 1.Given an object O and a recursion limit L, a general algorithm for following it can be expressed like so:
Inputs: - O (Object): an object that may be followable - L (integer): a recursion limit.
Outputs: - Error OBJECT_HAS_UNKNOWN_FOLLOWERS_COLLECTION - Error MAX_RECURSION_LIMIT - Success
(1) Initialize a variable INBOX. (2) If O.followers is not present, optionally return an error OBJECT_HAS_UNKNOWN_FOLLOWERS_COLLECTION. (3) If O.inbox is present, set INBOX to the referenced IRI. (4) If INBOX is unset, then initialize a variable R whose initial value is O. (5) While INBOX is unset: (5.1) Check that L > 0. If false, return an error MAX_RECURSION_LIMIT. (5.2) Dereference R.attributedTo and set the variable R to this referenced resource. (5.3) If R.inbox is present, set INBOX to the referenced IRI. (5.4) Set the variable L to the value L - 1. (6) Initialize a document ACTIVITY: (6.1) ...whose actor is yourself (6.2) ...whose type is Follow (6.3) ...whose object is O (7) Make an HTTP POST request whose target is INBOX and whose body is ACTIVITY.
Additional requirements for the ACTIVITY can be defined at a protocol level, but the exact protocol and those requirements are out-of-scope for this FEP. The scope of this FEP is limited to determining which objects can be followed, and where to send the Follow.
"},{"location":"fep/efda/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/f06f/","title":"FEP-f06f: Object observers","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2025-02-18 #503 Discussions codeberg"},{"location":"fep/f06f/#summary","title":"Summary","text":"Object observer is an ActivityPub actor that can be followed to receive object updates.
This proposal is intended to complement FEP-bad1: Object history collection.
"},{"location":"fep/f06f/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/f06f/#observers","title":"Observers","text":"Object observer is a followable actor. It doesn't perform any activities on its own, but activities that affect the observed object are forwarded to its followers. Only objects and collections can be observed.
Object observer MUST have an observerOf property specifying the observed object, and it SHOULD have an Application type.
Objects can specify their observers using the observer property.
Object observer can be created with ActivityPub client by publishing a Create activity with user's actor as its actor and with observer actor as its object.
Implementers MAY use a single cryptographic key for signing all HTTP requests made by observers on a server.
"},{"location":"fep/f06f/#use-case-subscribing-to-a-conversation","title":"Use case: subscribing to a conversation","text":"When conversation is represented by a collection, a collection observer can be created to support conversation subscriptions.
This actor can be attached to a collection via observer property, and can forward Add and Remove activities that modify it.
If forwarding is not desirable, object observers can use Announce activity to distribute observed activities.
Example of an observer actor:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"type\": \"Application\",\n \"id\": \"https://server.example/objects/123456/observer\",\n \"inbox\": \"https://server.example/objects/123456/observer/inbox\",\n \"outbox\": \"https://server.example/objects/123456/observer/outbox\",\n \"observerOf\": \"https://server.example/objects/123456\"\n}\n"},{"location":"fep/f06f/#references","title":"References","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/f15d/","title":"FEP-f15d: Context Relocation and Removal","text":"Authors Status Date received Tracking issue Discussions Repository Julian Lam julian@nodebb.org, Felix Ableitner me@nutomic.com, Rimu Atkinson rimu@rimu.geek.nzDRAFT 2026-01-12 #751 Discussions codeberg"},{"location":"fep/f15d/#summary","title":"Summary","text":"Threaded applications often have the need to move and remove content between groups/communities for curation purposes (i.e. resolving miscategorization, spam, etc.)
This is an extension of the Resolvable Contexts tree of FEPs.
"},{"location":"fep/f15d/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in [RFC-2119].
"},{"location":"fep/f15d/#assumptions","title":"Assumptions","text":""},{"location":"fep/f15d/#nomenclature","title":"Nomenclature","text":"The Forums and Threaded Discussions Task Force (ForumWG) has identified a common nomenclature when referring to organized objects in a threaded discussion model. The two terms used in this FEP are described below:
"},{"location":"fep/f15d/#context","title":"Context","text":"A group of related objects (i.e. via reply-association) is referred to as a \"context\". Other terms for this concept would be \"topic\" or \"thread\".
"},{"location":"fep/f15d/#audience","title":"Audience","text":"Contexts (see above) can be further grouped together into \"audiences\". Other terms for this concept would be \"community\", \"category\", or \"forum\". In ActivityPub, audiences usually take the form of a Group Actor type.
ActivityPub implementations differ in how they represent the aggregated collection of threaded objects.
Some implementations represent these collections as a distinct abstraction (e.g. a context.)
Others make no such distinction and represent them via the root-level object (e.g. link aggregators.)
The resolvable contexts tree of FEPs requires that abstraction in order to communicate actions pertaining to it in an explicit manner.
For more information, see FEP 7888.
"},{"location":"fep/f15d/#relocation","title":"Relocation","text":"When a publisher relocates a context from one audience to another, a Move activity is published to both audiences' followers.
{\n @context: \"https://www.w3.org/ns/activitystreams\",\n id: \"https://example.social/context/123#activity/move/<timestamp>\",\n type: \"Move\",\n actor: \"https://example.social/uid/1\",\n to: [\"https://www.w3.org/ns/activitystreams#Public\"],\n cc: [\n \"https://example.social/audience/1/followers\",\n \"https://example.social/audience/2/followers\",\n ],\n object: \"https://example.social/context/1\",\n origin: \"https://example.social/audience/1\",\n target: \"https://example.social/audience/2\",\n}\n Note the following properties:
actor is the user actor carrying out the action; typically a moderator (see Security Considerations)cc contains the follower collections of both the outgoing and incoming audiencesobject is the resolvable contextorigin is the outgoing audiencetarget is the incoming audienceAudience wrapper","text":"Implementations MAY wrap the Move activity in an Announce activity. Receivers are recommended to support both types.
There are two ways to signal that a context is removed from an audience.
Note
A removal is functionally different from a deletion. You cannot delete content that is originally from another server, as you are not the owner, but you are able to remove it from yours.
"},{"location":"fep/f15d/#resolvable-context-variant","title":"Resolvable context variant","text":"When a publisher removes a context from an audience, a Remove activity is published to the audience's followers.
{\n @context: \"https://www.w3.org/ns/activitystreams\",\n id: \"https://example.social/context/123#activity/remove/<timestamp>\",\n type: \"Remove\",\n actor: \"https://example.social/uid/1\",\n to: [\"https://www.w3.org/ns/activitystreams#Public\"],\n cc: [\"https://example.social/audience/1/followers\"],\n object: \"https://example.social/context/1\",\n target: \"https://example.social/audience/1\",\n}\n"},{"location":"fep/f15d/#group-federation-variant","title":"Group federation variant","text":"This sub-section is non-normative
Implementations may also handle the federated removal of objects by federating out a Delete referencing the root-level object. This activity is further wrapped in an Announce activity per FEP 1b12.
{\n \"actor\": \"http://ds9.lemmy.ml/u/lemmy_alpha\",\n \"to\": [\"https://www.w3.org/ns/activitystreams#Public\"],\n \"object\": \"http://ds9.lemmy.ml/post/1\",\n \"cc\": [\"http://enterprise.lemmy.ml/c/main\"],\n \"audience\": \"http://enterprise.lemmy.ml/u/main\",\n \"type\": \"Delete\",\n \"id\": \"http://ds9.lemmy.ml/activities/delete/f2abee48-c7bb-41d5-9e27-8775ff32db12\"\n}\n Example from Join Lemmy: Section 30 \"Federation\" \u2014 \"Delete Post or Comment\"
"},{"location":"fep/f15d/#security-considerations","title":"Security Considerations","text":""},{"location":"fep/f15d/#same-origin-check","title":"Same-Origin Check","text":"As per FEP fe34, the Remove and Move events outlined in this FEP are assumed to be authentic if the actor and the audience(s) in question (target and if applicable, origin) are same-origin.
FEP-1b12: Group federation describes the use of an OrderedCollection referenced in an audience's attributedTo to represent an audience's moderators.
If the actor of the Move or Remove activities is not same-origin to the audience(s) in question, this collection SHOULD be cross-referenced for authorization.
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/f1d5/","title":"FEP-f1d5: NodeInfo in Fediverse Software","text":"Authors Status Date received Date final Tracking issue Discussions Repository CJ cjslep@gmail.com, silverpill @silverpill@mitra.socialFINAL 2020-12-13 2023-06-02 #50 - codeberg"},{"location":"fep/f1d5/#summary","title":"Summary","text":"NodeInfo is a protocol intended to standardize upon a way to provide server-level metadata to the public. This enables tools and clients to utilize this metadata to assess server health or facilitate end-users choices about servers and software to use on the Fediverse.
"},{"location":"fep/f1d5/#history","title":"History","text":"NodeInfo was developed prior to the ActivityPub protocol targeted for use by diaspora, friendica, and redmatrix software. Some of the original protocols it encapsulated include diaspora, pumpio, and gnusocial.
The NodeInfo specification is incredibly strict in its schema, often requiring regex-validation and a closed set of enumerated possible values. As an objection to this, the NodeInfo2 fork was created as a form of criticism by removing some validation of fields and with some logical restructuring of the metadata. Building off of NodeInfo and NodeInfo2, ServiceInfo was briefly explored.
This FEP does not attempt to document the specific protocol details. For that, see the NodeInfo and NodeInfo2. It attempts to clarify the history and identify shortcomings with the current approaches, to bring context to developers of Fediverse Software.
"},{"location":"fep/f1d5/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this specification are to be interpreted as described in RFC-2119.
Fediverse software SHOULD implement NodeInfo.
"},{"location":"fep/f1d5/#caveats","title":"Caveats","text":"At the time of this FEP's writing, the current objections to the current state of NodeInfo that have been identified by the community are below. Note that any technical alternatives identified are meant to be illustrative and not prescriptive:
software.name regex is unnecessarily strict. For example, no uppercase letters, no spaces, no non-English-alphabet, and no special characters besides hyphen are permitted.software.version field is required, which is unnecessarily strict. Forcibly requiring software to divulge version information is potentially a security issue.inbound and outbound elements are specified as a closed set of enums instead of a simple string. Protocol versioning manifests as renaming, having to add a new enum, which results in unclear version management.openRegistrations concept due to it being required.metadata is too lax.usage.users is not denormalized, such that implementations can provide custom pairs of (activity counts, time period in days) that make sense for the software.usage.users assumes that user identity is tied to a specific instance of running software. It is unclear how to count total users when user identity is: spread across multiple servers, spread across multiple groups, or present within multiple collections of users. Multiple software instances could each have a reasonable claim to counting the user as \"using\" their software, which globally results users being counted more than once.usage.users activity counts likewise assume that user identity is tied to a specific instance of running software. For the same reasons above, where the total user counts may result in duplicate counts of the same user across all software running, the activity counts activeHalfYear and activeMonth may also result in a globally inflated count.activeHalfyear and activeMonth are ill-named properties for describing the time periods of 180 days and 30 days, respectively. A \"half of one year\" is 180 days 0% of the time and roughly 182.5 days only 75% of the time. A month is 30 days only 33% of the time.localPosts and localComments are not denormalized into pairs of (kind, counts) for software that, for example, hosts audio files, hosts videos, or software that does not have comments, or does not have posts.localPosts and localComments are required, which is problematic for software that does not have comments, or does not have posts.This list is not comprehensive:
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/f228/","title":"FEP-f228: Backfilling conversations","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2025-02-17 #500 Discussions codeberg"},{"location":"fep/f228/#summary","title":"Summary","text":"The most common conversation backfill method is based on recursive retrieval of posts indicated by inReplyTo property and posts contained in replies collections. This is inefficient and stops working if any node in the reply tree becomes inaccessible.
FEP-7888: Demystifying the context property suggests using the context property for grouping related objects (such as posts in a conversation). This property can resolve to a collection, which can be used for efficient backfilling without recursion.
Two different implementations of context collection exist: collection of posts and collection of activities.
The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/f228/#collection-of-posts","title":"Collection of posts","text":"This collection represents a thread and contains all posts in a conversation, from the perspective of the conversation owner.
It is an OrderedCollection, and the order of items is chronological. It MUST contain at least one item, the top-level post. This post MUST have a context property referring to the collection. Other posts might not have this property.
When context property is present on a post, it MUST resolve to a collection of posts.
There is a difference between contents of this collection and a reply tree defined by inReplyTo and replies relationships, because conversation owner might choose to not include certain replies. When a reply is deleted by its author, the sub-replies MAY remain in the thread collection.
Note
ActivityPub requires ordered collections to be presented in reverse chronological order. However, an erratum was proposed to relax this requirement.
"},{"location":"fep/f228/#collection-of-activities","title":"Collection of activities","text":"This collection contains all activities related to posts in a conversation, including but not limited to:
CreateUpdateDeleteLikeIt is an OrderedCollection, and the order of items is chronological. It MUST contain at least one item, the Create activity for the top-level post. This activity MUST have a context property referring to the collection. Other activities might not have this property.
When context property is present on an activity, it MUST resolve to a collection of activities.
In Conversation Containers this collection would coincide with the conversation container. It will contain Add activities in addition to other activities.
history property","text":"Collections described in this document can be implemented separately.
If both of them are implemented, the history property can be added to a collection of posts, indicating a corresponding collection of activities. This property is defined in FEP-bad1: Object history collection, although the use case here differs from the one described in that proposal.
contextHistory property","text":"contextHistory property can be used to make a reference from a post to a collection of activities.
After top-level post of a conversation is discovered, the whole conversation can be retrieved using the following algorithm:
contextHistory property is present, retrieve collection of activities and stop.context property is present, retrieve collection of posts and stop.replies property is present, retrieve collection of replies, and repeat this step for every reply.Collection of posts:
Collection of activities:
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/fb2a/","title":"FEP-fb2a: Actor metadata","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.comDRAFT 2022-12-09 #45 - codeberg"},{"location":"fep/fb2a/#summary","title":"Summary","text":"It is useful for actors to publish additional structured information about themselves without necessarily defining an extension property or additional vocabulary. This FEP describes a way for actors to publish generic key-value pairs representing their metadata.
"},{"location":"fep/fb2a/#history","title":"History","text":"Mastodon v2.4.0 (March 2018) implemented \"bio fields\" [1], a feature that allows adding structured data to profiles. This feature was federated via the attachment field, filtering for array items that had a type of PropertyValue derived from schema.org's vocabulary. Each item used name from the ActivityStreams Vocabulary, and value from the schema.org context. The schema.org namespace was defined as schema and (erroneously) mapped to http://schema.org# (instead of http://schema.org/ or https://schema.org) within the JSON-LD context property.
Misskey (December 2018) implemented \"user fields\" [2], following the same federation logic as Mastodon (filtering for a type of PropertyValue, then taking name and value).
Pleroma (August 2019) implemented \"custom profile fields\" [3], following the same federation logic as Mastodon (filtering for a type of PropertyValue, then taking name and value).
Rather than depending on an additional (and unnecessary) vocabulary, it makes sense to define a more \"native\" way of expressing the same idea of a key-value pair representing structured metadata about the actor. To this end, this FEP proposes using the existing Note type from the ActivityStreams 2.0 Vocabulary (instead of schema.org's PropertyValue), as well as the existing content property (instead of schema.org's value). Note that the name property exists within both the ActivityStreams 2.0 Vocabulary and the schema.org vocabulary, with largely the same semantic meaning; however, the use of schema.org vocabulary is out of scope of this FEP.
Thus, we can define a standard for actor metadata, largely drawing from prior art.
"},{"location":"fep/fb2a/#2-defining-generic-actor-metadata-as-key-value-pairs","title":"2. Defining generic actor metadata as key-value pairs","text":"General-purpose actor metadata fields SHOULD be included in the attachment array on the actor. If a more specific property exists and is a better fit for the specific metadata being expressed, then implementations MAY use that instead of or in addition to the more generic actor metadata.
Note.name representing the name (key) of the field.content representing the content (value) of the field.Actor metadata fields may also take the form of a link rather than a content value.
Link.href representing the value of the link.name representing a label for the link.rel values if an appropriate link relation exists, such as \"me\".(This section is non-normative.)
Existing implementations currently using the incorrect IRIs http://schema.org#PropertyValue and http://schema.org#value may wish to maintain backwards compatibility during a transitional period by serving both legacy representations as well as representations according to this FEP. The following algorithm may be used to support the legacy implementations while also favoring the implementation within this FEP:
attachment array for items of type Note or Link. Take name and content from each remaining item if the type is Note, or take name and href if the type is Link. If the type is Link and the rel contains \"me\", attempt to verify this link using rel-me verification.attachment), filter the attachment array for items of type http://schema.org#PropertyValue. Take name and http://schema.org#value from each remaining item. If name is a duplicate of an existing name, ignore the item.After some transitional period, implementations may wish to simplify their logic by filtering only for items of type Note and drop support for http://schema.org#PropertyValue, http://schema.org#value, and the schema.org context entirely (assuming those implementations do not use any other vocabulary from the schema.org context).
Consider a profile which has the following profile fields:
Pronouns\n: they/them\n\nMy portfolio\n: https://example.com/\n A legacy implementation might currently serialize these as such:
{\n \"@context\": [\n \"https://www.w3.org/ns/activitystreams\",\n {\n \"sc\": \"http://schema.org#\"\n \"PropertyValue\": \"sc:PropertyValue\",\n \"value\": \"sc:value\"\n }\n ],\n \"id\": \"https://social.example/someone\"\n \"type\": \"Person\",\n \"attachment\": [\n {\n \"type\": \"PropertyValue\",\n \"name\": \"Pronouns\",\n \"value\": \"they/them\"\n },\n {\n \"type\": \"PropertyValue\",\n \"name\": \"My portfolio\",\n \"value\": \"<a href=\"https://example.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">example.com</span><span class=\"invisible\"></span></a>\"\n }\n ]\n}\n For implementations that do not include the same incorrect IRI term mapping in their contexts, compaction would result in the following:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://social.example/someone\"\n \"type\": \"Person\",\n \"attachment\": [\n {\n \"type\": \"http://schema.org#PropertyValue\",\n \"name\": \"Pronouns\",\n \"http://schema.org#PropertyValue\": \"they/them\"\n },\n {\n \"type\": \"http://schema.org#PropertyValue\",\n \"name\": \"My portfolio\",\n \"http://schema.org#PropertyValue\": \"<a href=\"https://example.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">example.com</span><span class=\"invisible\"></span></a>\"\n }\n ]\n}\n Implementation according to this FEP might result in a simpler and more semantically correct serialization but equivalent representation like so:
{\n \"@context\": \"https://www.w3.org/ns/activitystreams\",\n \"id\": \"https://social.example/someone\"\n \"type\": \"Person\",\n \"attachment\": [\n {\n \"type\": \"Note\",\n \"name\": \"Pronouns\",\n \"content\": \"they/them\"\n },\n {\n \"type\": \"Link\",\n \"name\": \"My portfolio\",\n \"href\": \"https://example.com\",\n \"rel\": [\"nofollow\", \"noopener\", \"noreferrer\", \"me\"]\n }\n ]\n}\n"},{"location":"fep/fb2a/#implementations","title":"Implementations","text":"CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/fe34/","title":"FEP-fe34: Origin-based security model","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.socialDRAFT implementation 2024-11-15 #445 Discussions codeberg"},{"location":"fep/fe34/#summary","title":"Summary","text":"Developing a comprehensive ActivityPub security framework based on the concept of web origin.
"},{"location":"fep/fe34/#rationale","title":"Rationale","text":"ActivityPub standard does not specify authentication and authorization mechanisms. However, in some cases it hints at the importance of an object's origin:
3. Objects
... Servers SHOULD validate the content they receive to avoid content spoofing attacks. (A server should do something at least as robust as checking that the object appears as received at its origin, but mechanisms such as checking signatures would be better if available).
7.3 Update Activity
... The receiving server MUST take care to be sure that the Update is authorized to modify its object. At minimum, this may be done by ensuring that the Update and its object are of same origin.
Implementations often rely on origin and ownership checks for determining the validity of activities and objects, but exact requirements are not documented and can be easily overlooked, leading to vulnerabilities such as GHSA-3fjr-858r-92rw.
This proposal attempts to formalize existing practices and provide guidance for implementers.
"},{"location":"fep/fe34/#requirements","title":"Requirements","text":"The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\", \"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be interpreted as described in RFC-2119.
"},{"location":"fep/fe34/#assumptions","title":"Assumptions","text":"The origin-based security model is designed for use in a network where a server is resposible for enforcing security boundaries between the hosted actors. Servers that publish objects without validation are not supported.
Object identifiers are assumed to be HTTP(S) URIs. The model can also be used with other kinds of identifiers, but that is not covered in this document.
"},{"location":"fep/fe34/#origin","title":"Origin","text":"Object identifiers can be grouped together into protection domains called \"origins\". This concept is similar to the \"web origin\" concept described in RFC-6454, and origins of object IDs are computed by the same algorithm.
The same-origin policy determines when a relationship between objects can be trusted. Different origins are considered potentially hostile and are isolated from each other to varying degrees. Actors sharing an origin are assumed to trust each other because all their interactions are mediated by a single piece of software operated by a single person or an organization.
"},{"location":"fep/fe34/#comparing-origins","title":"Comparing origins","text":"uri-scheme be the scheme component of the URI, converted to lowercase.uri-host be the host component of the URI, converted to lowercase.uri-port be the default port for the protocol given by uri-scheme. Otherwise, let uri-port be the port component of the URI.(uri-scheme, uri-host, uri-port).Origins are the same if they have identical schemes, hosts, and ports.
"},{"location":"fep/fe34/#authentication","title":"Authentication","text":"Authentication is the process of verifying the origin of an ActivityPub object. It is performed in order to protect an application from spoofing attacks.
Objects can be authenticated using the following methods:
If an object can't be authenticated, it MUST be discarded.
Fetching from an origin is the primary authentication method, and other authentication methods described in this document depend on it. Consumers SHOULD attempt to fetch the object from its origin if other authentication methods are not available.
"},{"location":"fep/fe34/#fetching-from-an-origin","title":"Fetching from an origin","text":"Non-anonymous ActivityPub objects can be authenticated by making an HTTP GET request with object's ID as the target.
The last URI in the chain of redirects is object's location. The location SHOULD match the ID of the retrieved object. If object's location and ID are different, they MUST have the same origin.
If the object is protected, the server MAY require an HTTP signature.
Servers MUST validate all objects received from clients. Any activity representing an action that actor is not authorized to perform MUST be rejected. Special attention needs to be paid to media uploads, because malicious actors might attempt to bypass the validation by uploading ActivityPub documents as media. If the server allows clients to upload arbitrary files, it MUST serve media from a different origin (e.g. from a different subdomain).
As an additional protection in cases where an attacker was able to bypass the validation, consumers MUST verify that the response to a GET request contains the Content-Type header with the application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\" or application/activity+json media type (see GHSA-jhrq-qvrm-qr36 for more information).
Servers MUST NOT serve objects until they are validated.
"},{"location":"fep/fe34/#signatures","title":"Signatures","text":"Signature-based authentication can be used when:
The ID of the public key (or the verification method) MUST have the same origin as the object's ID.
Servers MUST NOT share secret keys with clients.
Servers MUST NOT allow clients to create or update objects representing public keys, including such objects embedded within actors and other objects. Public keys can be identified by their properties publicKeyPem and publicKeyMultibase. Embedded public keys with a different origin are permitted.
In order to minimize damage in the event of a key compromise or insufficient validation, consumers MUST verify that the signing key has the same owner as the signed object. Consumers MUST also confirm the ownership of the key by verifying a reciprocal claim.
Warning
JSON-LD consumers might be tricked into processing a specially crafted JSON object without publicKeyPem and publicKeyMultibase properties as a public key. Protections against attacks of that kind are not described in this document.
In some cases, an embedded object can be trusted when its wrapping object is trusted:
Authorization is the process of verifying permission to create, read, update or delete an object.
"},{"location":"fep/fe34/#ownership","title":"Ownership","text":"Ownership is indicated by a property of an ActivityPub object. The name of this property differs depending on the object class:
id property.actor property, which describes the actor that performed the activity. This actor is considered to be the owner of the activity.owner and controller properties.attributedTo property, which describes the actor to which the object is attributed. This actor is considered to be the owner of the object.In some cases ownership might be implicit. Examples:
replies collection is owned by the actor to which the post is attributed.Anonymous objects are not supposed to have an owner.
Applications SHOULD use the following algorithm to determine the owner of an object:
Link, return error.Object nor Collection, and the object has an attributedTo property, return error.Actor, return the value of the id property.VerificationMethod, return the value of the controller property.PublicKey, return the value of the owner property.Activity, return the value of the actor property.Object or Collection, return the value of the attributedTo property.The owner of an object MUST be an actor.
Identifier of an object and identifier of its owner MUST have the same origin.
Warning
According to Activity Vocabulary, actor and attributedTo properties can contain references to multiple actors. These scenarios are not covered by this document and implementers are expected to determine the appropriate authorization procedures on a case-by-case basis.
Owners are the same if their identifiers are identical after conversion of their schemes and hosts to lowercase.
"},{"location":"fep/fe34/#create-update-and-delete","title":"Create, update and delete","text":"The actor that creates an object MUST be its owner.
The owner of an object is permitted to modify and delete it. This permission might also be specified with reciprocal claims.
If the owner of an activity that modifies or deletes an object doesn't have a permission to perform the operation, the activity MUST be rejected. If such activity is received from another server and the permission can not be verified, the recipient MAY accept the activity if its actor and the owner of the affected object have the same origin.
Examples:
Update and Delete activities, and objects indicated by their object property are expected to have the same owner.Undo activity and object indicated by its object property are expected to have the same owner.Add and Remove activities, and objects indicated by their target property are expected to have the same owner.Announce and Like activities don't modify objects indicated by their object property, therefore their owners can be different.When a protected object is retrieved, the server MUST verify that the GET request contains an HTTP signature created using a key whose owner belongs to object's intended audience.
The server MAY require a signature even if the object is public. In that case, the request can be signed with a key owned by a server actor.
Servers that implement proxyUrl endpoint MUST ensure that access to objects is restricted to actors that belong to intended audiences of these objects.
"},{"location":"fep/fe34/#ownership-transfer","title":"Ownership transfer","text":"When ownership changes, the new owner ID MUST have the same origin as the old owner ID.
"},{"location":"fep/fe34/#reciprocal-claims","title":"Reciprocal claims","text":"Claims are considered reciprocal when one object specifies a claim that is accompanied by a reverse claim specified in another object.
Examples:
Relationships between objects with different origins are possible, but they MUST be confirmed with reciprocal claims made by both origins. In that case, the same-origin policy can be bypassed.
Examples:
Move activity if the migrating actor is included in the target actor's alsoKnownAs.CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"},{"location":"fep/fffd/","title":"FEP-fffd: Proxy Objects","text":"Authors Status Date received Tracking issue Discussions Repository Adam R. Nelson adam@nels.onl, Ryan Barrett (administrative only)DRAFT 2023-04-29 #95 - codeberg"},{"location":"fep/fffd/#summary","title":"Summary","text":"A proxy object is an [ActivityPub] object that is semantically identical to another entity, which may exist on another, non-ActivityPub protocol. For example, an ActivityPub-to-Nostr bridge creates Actors and Notes that are proxies for Nostr users and notes.
This document describes a data format to identify proxy objects and to specify the ActivityPub and non-ActivityPub entities they are equivalent to, with the intention that multi-protocol clients will automatically merge objects with their proxies, hiding the implementation details of bridges and cross-protocol publishing from users.
"},{"location":"fep/fffd/#1-requirements","title":"1. Requirements","text":"The key words \"MUST\", \"SHOULD\", and \"MAY\" are to be interpreted as described in [RFC2119].
"},{"location":"fep/fffd/#2-rationale","title":"2. Rationale","text":"This section is non-normative.
Many Fediverse servers speak multiple protocols besides ActivityPub, such as Nostr or Diaspora, and simultaneously publish posts across multiple protocols. Additionally, bridge servers exist to relay posts between ActivityPub and other protocols such as Nostr.
A use case also exists for creating a duplicate of an ActivityPub object: moving an account to a new instance. Posts copied to a user's new account should be marked as duplicates of the originals, in a way that preserves the original posts' likes and replies.
Despite these use cases, there is currently no standard way to communicate that an ActivityPub activity is a copy (or proxy) of a post on another protocol.
Consider this scenario:
Proxy objects provide a potential solution to this problem.
"},{"location":"fep/fffd/#3-format","title":"3. Format","text":"FEP-fffd does not define any new vocabulary or @context entries. Instead, it further defines the meaning of Links in the url property of an Object when they have a rel property of \"alternate\" or \"canonical\".
Each Link in url with \"rel\": \"alternate\" or \"rel\": \"canonical\" is called a proxy link. Any Object with one or more proxy links is called a proxy object. The referent of a proxy link is called a proxied object, and SHOULD be considered semantically identical to the parent proxy object, modulo the limitations described in section 4.
A proxied object is not necessarily an ActivityPub object, or even an object accessible via a network request; its meaning is determined based on its protocol. The protocol and format of the proxied object are determined by the proxy link's URI scheme and mediaType; well-known protocols and defaults are defined in section 5. An application SHOULD ignore proxied objects in protocols or formats that the application does not understand.
If a proxy link has \"rel\": \"canonical\", it indicates that its proxied object is the canonical (original, authoritative) version of the proxy object. A proxy object MUST NOT have more than one proxy link with \"rel\": \"canonical\". This property SHOULD be used by bridges that relay posts made by third parties, to indicate that the bridged post is not the original. It SHOULD NOT be used by servers that publish to multiple protocols at once; in this case no one version of an object is more authoritative than another.
When a conforming application encounters a proxy object, it may merge it with its proxy objects under certain circumstances.
To merge a proxy object and its proxied object(s) means to display all of these objects as a single entity (such as a user or a post), while combining all collections and metadata belonging to these objects:
In some circumstances, an application may encounter malformed or malicious proxy links that could misrepresent objects not owned by the links' author, or it may encounter proxy links whose referents are malformed or missing. These situations sometimes prevent merging.
alsoKnownAs each other.alsoKnownAs each other.Several protocols are named in this document, but interaction with these protocols is left intentionally underspecified, as the behavior of non-ActivityPub protocols is outside the scope of this FEP. If a proxy link's URI scheme and/or mediaType match a protocol named in this section, a conforming application SHOULD either use the matching protocol to access the proxied object or ignore the proxy link entirely, but it MUST NOT interpret the link as a proxy link for a different protocol or format.
nostr: URI scheme, as defined in [NIP-21]. The identifiers used in these URIs MUST be \"bare\" NIP-19 identifiers starting with npub1 or note1. The npub1 identifier type MUST be used only in proxy links for Actors.diaspora: URI scheme, following the format defined in [diaspora:// URI scheme].did: URI scheme, as defined in [DID URL Syntax], and MUST be used only in proxy links for Actors.at URI scheme, as defined in [AT URI Scheme]. AT Repositories, Collections, and Records MUST be used only in proxy links for ActivityPub Actors, Collections, and Objects, respectively. Repository URIs starting with at://did: SHOULD be considered identical to the did: URIs they contain; including both a did: link and an at://did: link for the same DID is redundant.ssb: URI scheme.application/rss+xml; the href should be the URL of the feed, followed by a URL fragment whose content is the <guid> value of an entry in the feed.application/atom+xml; the href should be the URL of the feed, followed by a URL fragment whose content is the <id> value of an entry in the feed.application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\" or application/activity+json; the href should point to an ActivityPub Object.If an application supports general-purpose transport protocols other than HTTP(S), such as Gemini or IPFS, it MAY interpret proxy links to these protocols in the same manner as it would interpret HTTP(S) proxy links, including applying these well-known media types.
"},{"location":"fep/fffd/#53-non-federated-web-content","title":"5.3. Non-federated Web Content","text":"By default, if a proxy link uses the http or https protocol, and either does not have a mediaType or has a mediaType of text/html, it is considered a link to some unspecified, application-defined non-federated Web content. An application MAY interpret this link as any kind of content or protocol other than one of the well-known protocols or formats defined in this section. Notably, this kind of proxy object MUST NOT be interpreted as an ActivityPub resource, even if the link responds with valid ActivityStreams data.
This default allows an ActivityPub object to proxy any non-federated Web content; for example, a Twitter-to-ActivityPub bridge may use a proxy link to a Twitter URL to identify a Note as a proxy object for a Twitter post.
"},{"location":"fep/fffd/#6-examples","title":"6. Examples","text":"This section is non-normative. The JSON-LD @context property is omitted for brevity.
A post relayed by a third-party Twitter-to-ActivityPub bridge. Because the canonical proxy link is also the only url entry, it should also be used as a clickable link to the original post.
{\n \"id\": \"http://twitter-bridge.example/status/1234\",\n \"type\": \"Note\",\n \"actor\": \"http://twitter-bridge.example/@jack\",\n \"content\": \"just setting up my twttr\",\n \"url\": {\n \"type\": \"Link\",\n \"rel\": \"canonical\",\n \"href\": \"https://twitter.com/jack/status/20\"\n }\n}\n A post published to ActivityPub, Diaspora, and Nostr simultaneously. Because there is one non-proxy Link in url with an https protocol, this non-proxy link should be used as a clickable link to the original post.
{\n \"id\": \"http://fediverse.example/status/1234\",\n \"type\": \"Note\",\n \"actor\": \"http://fediverse.example/@alice\",\n \"content\": \"Hello, world!\",\n \"url\": [{\n \"type\": \"Link\",\n \"href\": \"https://fediverse.example/@alice/1234\"\n }, {\n \"type\": \"Link\",\n \"rel\": \"alternate\",\n \"href\": \"diaspora://alice@fediverse.example/post/deadbeefdeadbeefdeadbeefdeadbeef\"\n }, {\n \"type\": \"Link\",\n \"rel\": \"alternate\",\n \"href\": \"nostr:note1gwdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef\"\n }]\n}\n An ActivityPub Note copied from a user's old instance to a new instance when moving to a new account.
{\n \"id\": \"http://newinstance.example/status/1234\",\n \"type\": \"Note\",\n \"actor\": \"http://newinstance.example/@alice\",\n \"content\": \"Hello, world!\",\n \"url\": [{\n \"type\": \"Link\",\n \"href\": \"https://newinstance.example/@alice/1234\"\n }, {\n \"type\": \"Link\",\n \"rel\": \"canonical\",\n \"mediaType\": \"application/ld+json; profile=\\\"https://www.w3.org/ns/activitystreams\\\"\",\n \"href\": \"https://oldinstance.example/status/5678\"\n }]\n}\n"},{"location":"fep/fffd/#references","title":"References","text":"nostr: URI scheme, 2023diaspora:// URI scheme] Benjamin Neff, diaspora* federation protocol, 2017CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
"}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml index 0e8be15..7c8390f 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -2,618 +2,618 @@