diff --git a/draft/index.html b/draft/index.html index 46abe8d..859484c 100644 --- a/draft/index.html +++ b/draft/index.html @@ -1307,15 +1307,6 @@ 2026-01-13 -FEP-8c13: Context-Authority Routing with Object Integrity Proofs for Restricted Threads -codeberg -Informational - -- -Discussions -2026-01-16 - - FEP-34c1: Collection Filtering using TREE Hypermedia Vocabulary codeberg Informational @@ -1495,6 +1486,15 @@ Discussions 2026-06-12 + +FEP-8c13: Context-Authority Routing with Object Integrity Proofs for Restricted Threads +codeberg +Informational + +#870 +Discussions +2026-06-27 + diff --git a/fep/8b32/fep-8b32.feature b/fep/8b32/fep-8b32.feature index c82ee9e..c729c9f 100644 --- a/fep/8b32/fep-8b32.feature +++ b/fep/8b32/fep-8b32.feature @@ -1,7 +1,7 @@ Feature: FEP-8b32: Object Integrity Proofs @fep-8b32 - Scenario: Signing document + Scenario: Signing a document Given document """ { @@ -27,8 +27,17 @@ Feature: FEP-8b32: Object Integrity Proofs """ And Ed25519 secret key "z3u2en7t5LR2WtQH5PfFqMqwVHBeXouLzo6haApm8XHqvjxq" And current time "2023-02-24T23:36:38Z" - When Signing the document for key "https://server.example/users/alice#ed25519-key" - Then The signed document is + When signing the document for key "https://server.example/users/alice#ed25519-key" + Then the canonicalized document is + """ + {"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/data-integrity/v2"],"actor":"https://server.example/users/alice","id":"https://server.example/activities/1","object":{"attributedTo":"https://server.example/users/alice","content":"Hello world","id":"https://server.example/objects/1","location":{"latitude":25.273962,"longitude":-71.184902,"type":"Place"},"type":"Note"},"type":"Create"} + """ + Then the canonicalized proof configuration is + """ + {"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/data-integrity/v2"],"created":"2023-02-24T23:36:38Z","cryptosuite":"eddsa-jcs-2022","proofPurpose":"assertionMethod","type":"DataIntegrityProof","verificationMethod":"https://server.example/users/alice#ed25519-key"} + """ + Then the combined hash is "cf63e2308ce7d1137667192c5c5e751ba7b1c6e3d5e746a7b717d309654ad1980793e8d97e2de4b989b2b2d7a5fae8cf941f102a03c0ecab00f03eaa2330c650" + Then the signed document is """ { "@context": [ @@ -66,7 +75,7 @@ Feature: FEP-8b32: Object Integrity Proofs @fep-8b32 Scenario: Verifying a signature - Given The signed document is + Given the signed document is """ { "@context": [ @@ -101,7 +110,7 @@ Feature: FEP-8b32: Object Integrity Proofs } } """ - And The actor + And the actor """ { "@context": [ diff --git a/fep/8b32/index.html b/fep/8b32/index.html index 0b4b25c..37de842 100644 --- a/fep/8b32/index.html +++ b/fep/8b32/index.html @@ -756,7 +756,8 @@

Objects identified using fragment identifiers SHOULD NOT contain integrity proofs.

Objects SHOULD NOT contain more than one integrity proof.

Proof verification

-

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.

+

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 a verifier encounters an integrity proof that uses a verification method or cryptosuite that it doesn't support, it SHOULD ignore the proof and try other authentication methods.

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.

@@ -770,7 +771,7 @@

Backward compatibility

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 compatibility 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.

Security considerations

Implementers using integrity proofs as an authentication mechanism are advised to follow the recommendations given in FEP-fe34: Origin-based security model.

diff --git a/fep/8c13/index.html b/fep/8c13/index.html index 82ff7e8..e78333f 100644 --- a/fep/8c13/index.html +++ b/fep/8c13/index.html @@ -1303,6 +1303,7 @@ Authors Status Date received +Tracking issue Discussions Repository @@ -1311,7 +1312,8 @@ Dmitry Skavish skavish@gmail.com DRAFT -2026-01-16 +2026-06-27 +#870 Discussions codeberg diff --git a/search/search_index.json b/search/search_index.json index 13b357d..3f1b96f 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":"

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:

  1. Fork this repository, and then clone it to your local machine. Check the Codeberg Cheat sheet on how to prepare your Pull Request.
  2. Think of a title for the FEP you want to submit.
  3. Compute the identifier of the FEP by computing the hash of the title. This can be done with following Unix command:
$ echo -n \"The title of my proposal\" | sha256sum | cut -c-4\nb3f0\n
  1. Create a subdirectory of fep/ using the identifier you just computed.
  2. Copy the FEP template (fep-xxxx-template.md) to this subdirectory and change the filename appropriately.
  3. Use the identifer as the \"slug\" when filling out the frontmatter.

  4. Write down your idea in the newly created file and commit it to a new branch in your repository (ex. fep-xxxx).

  5. When you are ready to submit the FEP, change the value of dateReceived field in the frontmatter to the current date.
  6. Create a discussion topic for your FEP. You can use ActivityPub category on the SocialHub forum.
  7. Add discussionsTo field containing the URL of the discussion topic to the front matter of your FEP.
  8. Create a Pull Request to complete Step 1 of FEP-a4ed: The Fediverse Enhancement Proposal Process. Further process is described in FEP-a4ed.

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 Type Tracking issue Discussions Received FEP-8b32: Object Integrity Proofs Implementation 9 #29 Discussions 2022-11-12 FEP-c390: Identity Proofs Implementation 3 #34 Discussions 2022-11-23 FEP-fb2a: Actor metadata Informational #45 - 2022-12-09 FEP-c118: Content licensing support Informational #57 - 2023-01-16 FEP-7888: Demystifying the context property Informational #68 Discussions 2023-03-14 FEP-888d: Using https://w3id.org/fep as a base for FEP-specific namespaces Informational #83 Discussions 2023-04-10 FEP-0ea0: Payment Links Implementation #88 Discussions 2023-04-18 FEP-fffd: Proxy Objects Informational #95 - 2023-04-29 FEP-4adb: Dereferencing identifiers with webfinger Informational #100 - 2023-05-13 FEP-c648: Blocked Collection Informational #123 - 2023-06-14 FEP-bad1: Object history collection Informational #124 - 2023-06-15 FEP-4ccd: Pending Followers Collection and Pending Following Collection Informational #129 Discussions 2023-06-21 FEP-d36d: Sharing Content Across Federated Forums Informational #125 - 2023-07-01 FEP-1970: Chat Links Informational #128 - 2023-07-04 FEP-ae97: Client-side activity signing Implementation 4 #148 Discussions 2023-08-14 FEP-0837: Federated Marketplace Implementation 4 #147 Discussions 2023-08-17 FEP-5feb: Search indexing consent for actors Informational #154 - 2023-09-06 FEP-dc88: Formatting Mathematics Informational #161 - 2023-09-12 FEP-d8c2: OAuth 2.0 Profile for the ActivityPub API Informational #165 Discussions 2023-09-17 FEP-7628: Move actor Informational #168 Discussions 2023-09-20 FEP-37f2: a policy for calls for consensus on SWICG group decisions Informational #184 - 2023-09-28 FEP-2677: Identifying the Application Actor Informational #190 - 2023-10-14 FEP-03c1: Actors without acct-URI Informational #205 - 2023-11-10 FEP-ef61: Portable Objects Implementation 5 #209 Discussions 2023-12-06 FEP-7502: Limiting visibility to authenticated actors Informational #218 - 2023-12-24 FEP-2c59: Discovery of a Webfinger address from an ActivityPub actor Informational #226 - 2024-01-04 FEP-3264: Federated Work Coordination Implementation #255 - 2024-01-31 FEP-c5a1: To-do's Implementation #256 - 2024-01-31 FEP-61cf: The OpenWebAuth Protocol Implementation 5 #263 - 2024-02-06 FEP-73cd: Migration User Stories Informational #265 - 2024-02-07 FEP-96ff: Explicit signalling of ActivityPub Semantics Informational #272 - 2024-02-17 FEP-9fde: Mechanism for servers to expose supported operations Informational #282 - 2024-03-14 FEP-e229: Best practices for extensibility Informational #293 - 2024-04-02 FEP-3b86: Activity Intents Informational #302 - 2024-04-19 FEP-7458: Using the replies collection Informational #307 - 2024-04-26 FEP-0391: Special collection proofs Informational #310 - 2024-04-29 FEP-db0e: Authentication mechanism for non-public groups Informational #313 - 2024-05-03 FEP-cd47: Federation-friendly Addressing and Deduplication Use-Cases Informational #319 Discussions 2024-05-31 FEP-5e53: Opt-out Preference Signals Informational #327 Discussions 2024-06-09 FEP-7952: Roadmap For Actor and Object Portability Informational #435 Discussions 2024-06-20 FEP-e3e9: Actor-Relative URLs Informational #346 - 2024-06-29 FEP-d9ad: Create Conformance Tests for Fediverse Enhancement Proposals Informational #350 - 2024-07-02 FEP-e965: Move Activity for Migrations and Announce Activity for Tombstone Events Informational #352 Discussions 2024-07-05 FEP-9091: Export Actor Service Endpoint Informational #358 Discussions 2024-07-08 FEP-6fcd: Account Export Container Format Informational #357 Discussions 2024-07-11 FEP-c551: Use ECMAScript Modules to Create Conformance Tests for Fediverse Enhancement Proposals Informational #366 - 2024-07-11 FEP-a5c5: Web Syndication Methods Informational #368 Discussions 2024-07-15 FEP-c4ad: Viewership History Informational #367 Discussions 2024-07-15 FEP-c893: DOAP Informational #372 Discussions 2024-07-15 FEP-eb48: Hashtags Informational #373 Discussions 2024-07-16 FEP-eb22: Supported ActivityStreams types with NodeInfo Informational #386 Discussions 2024-07-25 FEP-c0e0: Emoji reactions Implementation 4 #384 Discussions 2024-08-08 FEP-c16b: Formatting MFM functions Informational #383 Discussions 2024-08-10 FEP-0499: Delivering to multiple inboxes with a multibox endpoint Informational #402 Discussions 2024-09-30 FEP-76ea: Conversation Threads Informational #407 Discussions 2024-10-04 FEP-1985: Signaling how an OrderedCollection is ordered Informational #417 Discussions 2024-10-10 FEP-268d: Search consent signals for objects Informational #421 Discussions 2024-10-12 FEP-b2b8: Long-form Text Informational #441 Discussions 2024-11-07 FEP-fe34: Origin-based security model Implementation #445 Discussions 2024-11-15 FEP-171b: Conversation Containers Implementation 3 #449 Discussions 2024-11-23 FEP-6606: ActivityPub client to server collections addressing conventions Informational #453 Discussions 2024-12-04 FEP-1311: Media Attachments Informational #459 Discussions 2024-12-08 FEP-7d8c: Documentation: Automation of FEP Informational #470 Discussions 2025-01-20 FEP-9967: Polls Implementation 2 #478 Discussions 2025-01-23 FEP-2277: ActivityPub core types Informational #485 Discussions 2025-01-31 FEP-a974: All Actor types should be followable Informational #487 Discussions 2025-02-05 FEP-efda: Followable objects Informational #492 Discussions 2025-02-13 FEP-f228: Backfilling conversations Implementation 13 #500 Discussions 2025-02-17 FEP-f06f: Object observers Implementation #503 Discussions 2025-02-18 FEP-dd4b: Quote Posts Informational #511 Discussions 2025-02-21 FEP-c180: Problem Details for ActivityPub Informational #531 Discussions 2025-03-11 FEP-2931: Representing context with a Collection Implementation 4 #534 Discussions 2025-03-22 FEP-5711: Inverse Properties for Collections Informational #539 Discussions 2025-03-24 FEP-044f: Consent-respecting quote posts Informational #547 Discussions 2025-04-03 FEP-1042: Peer to Peer Fediverse Identities Informational #546 Discussions 2025-04-03 FEP-4f05: Soft Deletion Informational #554 Discussions 2025-04-15 FEP-8a8e: A common approach to using the Event object type Informational #565 Discussions 2025-04-23 FEP-82f6: Actor statuses Informational #600 Discussions 2025-05-12 FEP-844e: Capability discovery Implementation 5 #624 Discussions 2025-06-14 FEP-b06c: ActivityPoll Informational #638 Discussions 2025-06-25 FEP-9098: Custom emojis Implementation #648 Discussions 2025-07-06 FEP-11dd: Context Ownership and Inheritance Informational #675 Discussions 2025-09-11 FEP-8967: Generating link previews for attached links Informational #681 Discussions 2025-09-16 FEP-1580: Move Actor Objects with a migration Collection Implementation #702 Discussions 2025-10-09 FEP-d8c8: BitTorrent Torrent Objects Informational #722 Discussions 2025-11-03 FEP-19b3: Specifying Properties of a Service Informational #719 Discussions 2025-11-04 FEP-22b6: Linking an ActivityPub Object to a HTML page and back Informational #730 Discussions 2025-11-12 FEP-f15d: Context Relocation and Removal Informational #751 Discussions 2026-01-12 FEP-ee3a: Exif metadata support Informational #752 Discussions 2026-01-13 FEP-8c13: Context-Authority Routing with Object Integrity Proofs for Restricted Threads Informational - Discussions 2026-01-16 FEP-34c1: Collection Filtering using TREE Hypermedia Vocabulary Informational #763 Discussions 2026-02-19 FEP-a427: Server Domain Migration Informational #767 Discussions 2026-02-25 FEP-fc48: Generic ActivityPub server Implementation #769 Discussions 2026-02-27 FEP-3ab2: ActivityPub Event Streaming API Informational #778 Discussions 2026-03-14 FEP-34ec: Notification Collection Endpoint Informational #782 Discussions 2026-03-15 FEP-c07e: add product type to object Implementation #786 Discussions 2026-03-15 FEP-db70: RemoveAll Collection Activity Informational #784 Discussions 2026-03-15 FEP-c195: JSONPath Filtering for ActivityPub Collection Retrieval Implementation 1 #790 Discussions 2026-03-17 FEP-f011: Full-Text Search Query Syntax for ActivityPub Implementation 1 #791 Discussions 2026-03-17 FEP-c81b: Agent Social Attribution for ActivityPub Informational #798 Discussions 2026-03-18 FEP-a1d1: ActivityPub Patch Implementation 1 #796 Discussions 2026-03-19 FEP-7b29: Federated Signup Requests Informational #822 Discussions 2026-03-26 FEP-1a11: Send Announces Containing Many Activities Informational #807 Discussions 2026-04-01 FEP-9f9f: Collections Informational #810 Discussions 2026-04-03 FEP-35b7: Fediverse Servers, Instances, and Tenants Informational #832 Discussions 2026-04-22 FEP-ae49: Semantic Routing for ActivityPub Implementation 3 #836 Discussions 2026-04-24 FEP-baf5: Administrator Collection Informational #848 Discussions 2026-05-22 FEP-bebd: Follow Invites Informational #854 Discussions 2026-06-05 FEP-5219: Groups and permissions Implementation #860 Discussions 2026-06-11 FEP-7aa9: Featuring recommendations using a dedicated collection Informational #862 Discussions 2026-06-12"},{"location":"final/","title":"Final","text":"Title Type Tracking issue Discussions Received Finalized FEP-a4ed: The Fediverse Enhancement Proposal Process Informational #201 - 2020-10-16 2021-01-18 FEP-8fcf: Followers collection synchronization across servers Informational #6 - 2020-10-24 2022-02-07 FEP-f1d5: NodeInfo in Fediverse Software Informational #50 - 2020-12-13 2023-06-02 FEP-400e: Publicly-appendable ActivityPub collections Informational #5 - 2021-02-16 2022-02-04 FEP-e232: Object Links Informational #14 Discussions 2022-08-01 2023-12-03 FEP-1b12: Group federation Informational #22 - 2022-11-12 2023-02-09 FEP-521a: Representing actor's public keys Implementation 6 #130 Discussions 2023-07-08 2025-06-14 FEP-67ff: FEDERATION.md Informational #157 Discussions 2023-09-05 2024-09-22 FEP-d556: Server-Level Actor Discovery Using WebFinger Implementation 7 #243 - 2024-01-20 2025-03-15 FEP-ae0c: Fediverse Relay Protocols: Mastodon and LitePub Informational #424 Discussions 2024-10-19 2025-03-14 FEP-0151: NodeInfo in Fediverse Software (2025 edition) Implementation 5 #599 Discussions 2025-05-12 2026-03-07"},{"location":"withdrawn/","title":"Withdrawn","text":"Title Type Tracking issue Discussions Received Withdrawn FEP-8c3f: Web Monetization Informational #3 - 2022-01-18 2023-10-27 FEP-2100: Unbound Group and Organization Informational #36 Discussions 2022-03-31 2025-04-06 FEP-5624: Per-object reply control policies Informational #18 Discussions 2022-08-23 2025-06-24 FEP-cb76: Content Addressed Vocabulary Informational #41 Discussions 2022-11-29 2025-04-07 FEP-2e40: The FEP Vocabulary Extension Process Informational #62 - 2023-02-13 2023-09-04 FEP-d767: Extend ActivityPub with Valueflows Informational #76 - 2023-04-02 2024-02-03 FEP-5bf0: Collection sorting and filtering Informational #80 - 2023-04-10 2025-06-14 FEP-612d: Identifying ActivityPub Objects through DNS Informational #89 - 2023-04-18 2025-10-10 FEP-a070: Ordered properties for plain JSON consumers Informational #121 - 2023-06-13 2026-04-10 FEP-07d7: A Custom URL Scheme and Web-Based Protocol Handlers for Linking to ActivityPub Resources Informational #171 - 2023-09-22 2025-04-22 FEP-6481: Specifying ActivityPub extension support with NodeInfo Informational #279 - 2024-03-12 2024-10-31 FEP-c7d3: Ownership Informational #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 Date final Tracking issue Discussions Repository silverpill @silverpill@mitra.social FINAL implementation 2025-05-12 2026-03-07 #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":"

Some softwares might wish to publish NodeInfo, but without revealing usage statistics. While the NodeInfo 2.1 schema specifies usage and usage.users as required properties, it does not require any additional properties within usage.users. Servers MUST NOT publish intentionally skewed usage statistics; instead, servers wishing to hide their usage statistics SHOULD publish an empty object:

{\n  \"usage\": {\n    \"users\": {}\n  }\n}\n
"},{"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. Attempting to use this information is very difficult to do reliably, and often hinders inteoperability. However, software version information MAY be used to block instances with known security vulnerabilities.

Instead of NodeInfo, protocol-specific capability discovery mechanisms could be used. 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:

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":""},{"location":"fep/0151/#references","title":"References","text":""},{"location":"fep/0151/#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/0391/","title":"FEP-0391: Special collection proofs","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 2024-04-29 #310 - codeberg"},{"location":"fep/0391/#summary","title":"Summary","text":"

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.

"},{"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.

"},{"location":"fep/0391/#outbox-claim","title":"Verifying presence in 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 in inbox","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.

"},{"location":"fep/0391/#followers-claim","title":"Verifying presence in 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:

"},{"location":"fep/0391/#following-claim","title":"Verifying presence in following","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:

"},{"location":"fep/0391/#liked-claim","title":"Verifying presence in liked","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.

"},{"location":"fep/0391/#likes-claim","title":"Verifying presence in 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.

"},{"location":"fep/0391/#shares-claim","title":"Verifying presence in 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.

"},{"location":"fep/0391/#replies-claim","title":"Verifying presence in 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

"},{"location":"fep/0391/#context-claim","title":"Verifying presence in 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

"},{"location":"fep/0391/#types-of-proofs","title":"Types of proofs","text":"

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.

"},{"location":"fep/0391/#replies-stamp","title":"Showing inclusion in 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:

On a Create activity where the object has inReplyTo set, the proof can be verified if all of the following are satisfied:

On 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:

Example 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:

On a Create activity where the object has context set, the proof can be verified if all of the following are satisfied:

On 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:

Example 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:

Example:

{\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:

Example:

{\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:

"},{"location":"fep/0391/#flow","title":"Obtaining and revoking stamps","text":"

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\"]

"},{"location":"fep/0391/#a-simpler-example-flow-for-issuing-and-revoking-a-like-stamp","title":"A simpler example flow for issuing and revoking a like stamp","text":"

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":""},{"location":"fep/0391/#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/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.

"},{"location":"fep/03c1/#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/044f/","title":"FEP-044f: Consent-respecting quote posts","text":"Authors Status Date received Tracking issue Discussions Repository Claire claire.fep-1d7d@sitedethib.com DRAFT 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.

"},{"location":"fep/044f/#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 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.

"},{"location":"fep/044f/#quote","title":"Representation of a quote post","text":"

A \u201cquote post\u201d is represented as an object with a quote (https://w3id.org/fep/044f#quote) attribute.

"},{"location":"fep/044f/#example","title":"Example","text":"
{\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.

"},{"location":"fep/044f/#compatibility-with-other-quote-implementations","title":"Compatibility with other quote implementations","text":"

(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.

"},{"location":"fep/044f/#example-of-quoteauthorization","title":"Example of 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

"},{"location":"fep/044f/#revoking-a-quoteauthorization","title":"Revoking a QuoteAuthorization","text":"

An approval stamp can be revoked by Deleteing the stamp.

"},{"location":"fep/044f/#QuoteRequest","title":"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.

"},{"location":"fep/044f/#example-quoterequest-activity","title":"Example 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.

"},{"location":"fep/044f/#example-accept","title":"Example 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      \"QuoteRequest\": \"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.

"},{"location":"fep/044f/#rejection","title":"Rejection","text":"

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.

"},{"location":"fep/044f/#acceptance","title":"Acceptance","text":"

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.

"},{"location":"fep/044f/#example-updated-note-object","title":"Example updated Note object","text":"
{\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      \"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 object for this object

"},{"location":"fep/044f/#revocation-of-a-quote-post","title":"Revocation of a quote post","text":""},{"location":"fep/044f/#revoking-a-previously-accepted-quote-post","title":"Revoking a previously-accepted quote post","text":"

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.

"},{"location":"fep/044f/#handling-a-revocation","title":"Handling a revocation","text":"

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.

"},{"location":"fep/044f/#opportunistic-re-verification-of-quote-approvals","title":"Opportunistic re-verification of quote approvals","text":"

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.

"},{"location":"fep/044f/#server-behavior-considerations","title":"Server behavior considerations","text":"

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.

"},{"location":"fep/044f/#security-considerations","title":"Security considerations","text":"

(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

"},{"location":"fep/044f/#implementations","title":"Implementations","text":""},{"location":"fep/044f/#references","title":"References","text":""},{"location":"fep/044f/#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/0499/","title":"FEP-0499: Delivering to multiple inboxes with a multibox endpoint","text":"Authors Status Date received Tracking issue Discussions Repository a DRAFT 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.

"},{"location":"fep/0499/#prior-art","title":"Prior art","text":"

(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:

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).

"},{"location":"fep/0499/#examples","title":"Examples","text":"

(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":""},{"location":"fep/0499/#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/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 "},{"location":"fep/07d7/#summary","title":"Summary","text":"

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

"},{"location":"fep/07d7/#21-intents","title":"2.1 Intents","text":"

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.

"},{"location":"fep/07d7/#22-example-links-using-the-scheme","title":"2.2 Example links using the scheme","text":"

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>

"},{"location":"fep/07d7/#3-protocol-handlers","title":"3. Protocol Handlers","text":""},{"location":"fep/07d7/#31-retrieving-hyperlinked-objects","title":"3.1 Retrieving hyperlinked objects","text":"

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:

"},{"location":"fep/07d7/#33-servers","title":"3.3 Servers","text":"

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.

"},{"location":"fep/07d7/#34-clients","title":"3.4 Clients","text":"

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.

"},{"location":"fep/07d7/#references","title":"References","text":""},{"location":"fep/07d7/#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/0837/","title":"FEP-0837: Federated Marketplace","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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 `OfferAgreement` activity\n  Alice ->> Bob: Alice sends `AcceptAgreement` 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:

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:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/0837\"\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    \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q11442\",\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    \"https://w3id.org/fep/0837\"\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\": \"Local organic carrots\",\n  \"content\": \"Great quality with all the rain, harvested yesterday.\",\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/Q81\",\n    \"resourceQuantity\": {\n      \"hasUnit\": \"pound\",\n      \"hasNumericalValue\": \"1\"\n    },\n    \"availableQuantity\": {\n      \"hasUnit\": \"pound\",\n      \"hasNumericalValue\": \"150\"\n    },\n    \"minimumQuantity\": {\n      \"hasUnit\": \"pound\",\n      \"hasNumericalValue\": \"5\"\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\": \"currencyAmount\",\n      \"hasNumericalValue\": \"0.89\"\n    }\n  },\n  \"unitBased\": \"true\",\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\": [\n    \"https://www.w3.org/ns/activitystreams\"\n  ],\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.

"},{"location":"fep/0837/#responding-to-a-proposal","title":"Responding to a proposal","text":""},{"location":"fep/0837/#agreements","title":"Agreements","text":"

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 AcceptAgreement activity back to the interested party.

In the second case, the proposer sends RejectAgreement activity. The interested party MAY send OfferAgreement activities many times until agreement is reached.

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 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 OfferAgreement activity:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/0837\"\n  ],\n  \"type\": \"OfferAgreement\",\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\": \"currencyAmount\",\n        \"hasNumericalValue\": \"30.00\"\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 AcceptAgreement activity MUST be the id of the OfferAgreement activity previously sent to the actor.

The AcceptAgreement activity MUST have a 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 AcceptAgreement activity.

The quantities specified in the finalized agreement MUST match the quantities specified in the Agreement object from the OfferAgreement 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    \"https://w3id.org/fep/0837\"\n  ],\n  \"type\": \"AcceptAgreement\",\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\": \"currencyAmount\",\n        \"hasNumericalValue\": \"30.00\"\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 RejectAgreement activity MUST be the id of the OfferAgreement activity previously sent to the actor.

Activity MAY contain content property indicating the reason for rejection.

Example:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/0837\"\n  ],\n  \"type\": \"RejectAgreement\",\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":""},{"location":"fep/0837/#references","title":"References","text":""},{"location":"fep/0837/#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/0ea0/","title":"FEP-0ea0: Payment Links","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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:

Payment links MUST be added to attachment array of an actor or an object.

"},{"location":"fep/0ea0/#examples","title":"Examples","text":"

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.

"},{"location":"fep/0ea0/#references","title":"References","text":""},{"location":"fep/0ea0/#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/1042/","title":"FEP-1042: Peer to Peer Fediverse Identities","text":"Authors Status Date received Tracking issue Discussions Repository Mauve Signweaver contact@mauve.moe DRAFT 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.

"},{"location":"fep/1042/#webfinger","title":"Webfinger","text":"

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.

"},{"location":"fep/1042/#url-links-in-ap-objects","title":"URL links in AP Objects","text":"

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.

"},{"location":"fep/1042/#gateways","title":"Gateways","text":"

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.

"},{"location":"fep/1042/#examples","title":"Examples","text":"

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":""},{"location":"fep/1042/#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/11dd/","title":"FEP-11dd: Context Ownership and Inheritance","text":"Authors Status Date received Tracking issue Discussions Repository Julian Lam julian@nodebb.org 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":"

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).

"},{"location":"fep/11dd/#what-is-this-for","title":"What is this for?","text":"

This proposal aims to extend these guidelines further by codifying:

  1. That a context must declare an owner via context.attributedTo.
  2. Where and when a context may be inherited by new objects.
"},{"location":"fep/11dd/#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/11dd/#context-ownership","title":"Context Ownership","text":"

A context MUST have an owner. The following are upgrades to FEP 7888 that pertain to ownership:

  1. A context MUST be resolvable.
  2. When resolved the context MUST contain:
  3. context.attributedTo MUST resolve to an actor (ActivityPub \u00a74.1 Actor Objects).
"},{"location":"fep/11dd/#example","title":"Example","text":"

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.)

"},{"location":"fep/11dd/#references","title":"References","text":""},{"location":"fep/11dd/#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/1311/","title":"FEP-1311: Media Attachments","text":"Authors Status Date received Tracking issue Discussions Repository Helge @helge@mymath.rocks DRAFT 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.

"},{"location":"fep/1311/#specifying-basic-properties","title":"Specifying basic properties","text":"

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.

"},{"location":"fep/1311/#properties-of-the-linked-file","title":"Properties of the linked file","text":"

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.

"},{"location":"fep/1311/#properties-of-an-image","title":"Properties of an image","text":"

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

where at least focalPoint is user defined. There are a lot of other properties one can consider for media, e.g.

In 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.

"},{"location":"fep/1311/#mixed-media-content","title":"Mixed media content","text":"

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":""},{"location":"fep/1311/#posts","title":"Posts","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":"FEP-1580: Move Actor Objects with a 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":"

(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:

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.

"},{"location":"fep/1580/#specification","title":"Specification","text":"

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.

"},{"location":"fep/1580/#terminology","title":"Terminology","text":"

As used in this document:

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.

"},{"location":"fep/1580/#preconditions","title":"Preconditions","text":"

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.

"},{"location":"fep/1580/#target-instance","title":"Target Instance","text":""},{"location":"fep/1580/#ingest","title":"Ingest","text":"

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.

"},{"location":"fep/1580/#examples_1","title":"Examples","text":"

(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.

"},{"location":"fep/1580/#3rd-party-instances","title":"3rd-Party Instances","text":"

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.

"},{"location":"fep/1580/#migration-routine","title":"Migration Routine","text":"

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.

"},{"location":"fep/1580/#passive-updates","title":"Passive Updates","text":"

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.

"},{"location":"fep/1580/#retroactive-migration","title":"Retroactive Migration","text":"

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:

"},{"location":"fep/1580/#pre-move-moderation","title":"Pre-Move Moderation","text":"

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:

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.

"},{"location":"fep/1580/#examples_2","title":"Examples","text":"

(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

"},{"location":"fep/1580/#moderation","title":"Moderation","text":"

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:

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.

"},{"location":"fep/1580/#performance","title":"Performance","text":"

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).

"},{"location":"fep/1580/#compatibility","title":"Compatibility","text":"

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.

"},{"location":"fep/1580/#supplementary-information","title":"Supplementary Information","text":""},{"location":"fep/1580/#acknowledgements","title":"Acknowledgements","text":"

Thank 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.

"},{"location":"fep/1580/#references","title":"References","text":""},{"location":"fep/1580/#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/#footnotes","title":"Footnotes","text":"
  1. 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

  2. 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

  3. Members of as:audience, as:to, as:bto, as:cc, as:bcc, and any other properties used for scoping visibility or delivery.\u00a0\u21a9

  4. \"Ownership\" in this document is defined according to FEP-fe34: Ownership \u21a9

  5. the CURIE for the collection is, maybe unhelpfully, migration:migration to refer to https://w3id.org/fep/1580/migration \u21a9

  6. \"Push\" mode migration, as defined in FEP-7628 \u21a9

  7. 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

  8. Until the next heading.\u00a0\u21a9

  9. 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

"},{"location":"fep/171b/","title":"FEP-171b: Conversation Containers","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social 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":"

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:

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.

"},{"location":"fep/171b/#top-level-post","title":"Top-level post","text":"

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.

"},{"location":"fep/171b/#interactions","title":"Interactions","text":"

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.

"},{"location":"fep/171b/#moderation","title":"Moderation","text":"

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).

"},{"location":"fep/171b/#backfilling","title":"Backfilling","text":"

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:

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":""},{"location":"fep/171b/#implementations","title":"Implementations","text":""},{"location":"fep/171b/#references","title":"References","text":""},{"location":"fep/171b/#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/1970/","title":"FEP-1970: Chat Links","text":"Authors Status Date received Tracking issue Discussions Repository John Livingston git@john-livingston.fr DRAFT 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:

Chat links MUST be added to the attachment array of an actor or an object.

"},{"location":"fep/1970/#examples","title":"Examples","text":""},{"location":"fep/1970/#basic-object-attachment","title":"Basic object attachment","text":"
{\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":""},{"location":"fep/1970/#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/1985/","title":"FEP-1985: Signaling how an OrderedCollection is ordered","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 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.

"},{"location":"fep/1985/#terms-defined","title":"Terms defined","text":"orderType 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
\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":""},{"location":"fep/1985/#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/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.

"},{"location":"fep/19b3/#property-values","title":"Property Values","text":"

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.

"},{"location":"fep/19b3/#suggested-property-values","title":"Suggested Property Values","text":"

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

"},{"location":"fep/19b3/#support","title":"Support","text":"

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.

"},{"location":"fep/19b3/#appendix-the-json-ld-problem","title":"Appendix: The JSON-LD problem","text":"

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.

"},{"location":"fep/19b3/#references","title":"References","text":""},{"location":"fep/19b3/#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/1a11/","title":"FEP-1a11: Send Announces Containing Many Activities","text":"Authors Status Date received Tracking issue Discussions Repository Rimu Atkinson contact@piefed.social DRAFT 2026-04-01 #807 Discussions codeberg"},{"location":"fep/1a11/#summary","title":"Summary","text":"

This proposal describes a mechanism for sending multiple activities in a single POST. The core idea is to extend FEP 1b12 slightly by changing the object property on Announce activities from an object to a list/array of objects.

"},{"location":"fep/1a11/#motivation","title":"Motivation","text":"

Currently, ActivityPub involves sending a HTTP POST request to followers whenever something notable happens. As a user can do a great number of notable things (posting content, liking content, following others) each minute and there can be thousands of instances to send to, a great many POST requests can be sent in a short amount of time.

For example if 5 people cast 20 votes and there are 500 instances, the instance hosting the community containing the posts being voted on must send 5 * 20 * 500 = 50,000 HTTP POSTs.

Each POST involves overhead and uses resources at both ends while it occurs. To reduce this overhead and resource use, this proposal describes a mechanism for sending multiple activities in a single POST.

Benefits include: decreased network traffic, higher efficiency, greater scalability, lowered carbon emissions and lower server costs.

"},{"location":"fep/1a11/#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.

For the purposes of this document, a \"Origin Server\" is the location where a user is logged in, and that sends the Actor's Activity. A \"Remote Server\" is another location on the Internet that will receive those Activities.

"},{"location":"fep/1a11/#proposal","title":"Proposal","text":"

The majority of FEP 1b12-based ActivityPub traffic involves the transfer of an Announce with a up/down vote in it. This FEP only addresses those activities - all other traffic is processed in the usual way.

Origin Servers SHOULD continue to send individual POSTs with a single Activity in them, just as they do now. For votes, which are not especially time-sensitive, they MAY instead keep the Activity unsent in a queue.

Origin Servers SHOULD include metadata with each queued Activity recording the community it is being sent from and the instance it is destined for.

Once an implementation-specific threshold is reached (e.g. a certain amount of time has passed or a queue has grown to a certain size) the Activities from each community will be batched together under one Announce (per community) and that batch federated to each destination instance.

A batch of Activities MUST be comprised of an Announce that has an object attribute that is an OrderedCollection of Like, Dislike, Undo(Like) or Undo(Dislike) activities.

Remote Servers SHOULD process the Activities' object property serially in the order provided.

The batch MUST be HTTP Signed by a Group actor, the community that generated the batch, which SHOULD be find-able using WebFinger. The signature SHOULD include the date the signing took place, to make replay attacks less likely. This method of signing is widely used on the Fediverse already.

Remote Servers MAY use HTTP Signature verification to ensure the integrity of the received Activity.

"},{"location":"fep/1a11/#post-example","title":"POST Example","text":""},{"location":"fep/1a11/#existing-old-style-posts","title":"Existing old-style POSTs","text":"
POST server.tld/inbox\nSignature: a69F3mfddd99dGjmvjdjjs81e12jn121Gkm1\nDigest: asdfasdfasdf\n{\n  \"actor\": \"https://lemmy.world/c/nostupidquestions\",\n  \"to\": [\n    \"https://www.w3.org/ns/activitystreams#Public\"\n  ],\n  \"object\": {\n    \"id\": \"https://lemmy.world/activities/like/aa6d9456-0960-4b9d15-561d1cef2991\",\n    \"actor\": \"https://lemmy.world/u/SpaceNo\",\n    \"object\": \"https://kbin.social/m/nostupidquestions@lemmy.world/t/433370/-/comment/2965\",\n    \"type\": \"Like\",\n    \"audience\": \"https://lemmy.world/c/nostupidquestions\"\n  },\n  \"cc\": [\n    \"https://lemmy.world/c/nostupidquestions/followers\"\n  ],\n  \"type\": \"Announce\",\n  \"id\": \"https://lemmy.world/activities/announce/e49d182c-db32-4a10-bb-c57de890cd9e\",\n  \"@context\": [\n     ...\n  ],\n}\n\n\nPOST server.tld/inbox\nSignature: b69F3mfddd99dGjmvjdjjs81e12jn121Gkm1\nDigest: asdfasdfasdf\n{\n  \"actor\": \"https://lemmy.world/c/nostupidquestions\",\n  \"to\": [\n    \"https://www.w3.org/ns/activitystreams#Public\"\n  ],\n  \"object\": {\n    \"id\": \"https://lemmy.world/activities/like/aa6d9456-094bf0-9d15-561d1cef299e\",\n    \"actor\": \"https://lemmy.world/u/Peter\",\n    \"object\": \"https://kbin.social/m/nostupidquestions@lemmy.world/t/433370/-/comment/2965\",\n    \"type\": \"Like\",\n    \"audience\": \"https://lemmy.world/c/nostupidquestions\"\n  },\n  \"cc\": [\n    \"https://lemmy.world/c/nostupidquestions/followers\"\n  ],\n  \"type\": \"Announce\",\n  \"id\": \"https://lemmy.world/activities/announce/e49d182c-db32-4a10-bb-c57de890cd9e\",\n  \"@context\": [\n     ...\n  ],\n}\n\n\nPOST server.tld/inbox\nSignature: c69F3mfddd99dGjmvjdjjs81e12jn121Gkm1\nDigest: asdfasdfasdf\n{\n  \"actor\": \"https://lemmy.world/c/nostupidquestions\",\n  \"to\": [\n    \"https://www.w3.org/ns/activitystreams#Public\"\n  ],\n  \"object\": {\n    \"id\": \"https://lemmy.world/activities/like/aa6d9456-0960-4bf0-9d15-56cef2994\",\n    \"actor\": \"https://lemmy.world/u/Rimu\",\n    \"object\": \"https://kbin.social/m/nostupidquestions@lemmy.world/t/433370/-/comment/2965\",\n    \"type\": \"Like\",\n    \"audience\": \"https://lemmy.world/c/nostupidquestions\"\n  },\n  \"cc\": [\n    \"https://lemmy.world/c/nostupidquestions/followers\"\n  ],\n  \"type\": \"Announce\",\n  \"id\": \"https://lemmy.world/activities/announce/e49d182c-db32-4a10-bb-c57de890cd9e\",\n  \"@context\": [\n     ...\n  ],\n}\n
"},{"location":"fep/1a11/#new-and-improved-fep-1a11-style-post","title":"New and improved fep-1a11-style POST","text":"
POST server.tld/inbox\nSignature: asdfasdfasdf\nDigest: asdfasdfasdf\n{\n  \"actor\": \"https://lemmy.world/c/nostupidquestions\",\n  \"to\": [\n    \"https://www.w3.org/ns/activitystreams#Public\"\n  ],\n  \"object\": {\n    \"type\": \"OrderedCollection\",\n    \"id\": \"https://lemmy.world/activities/abcd9456-0960-4b9d15-561d1cef2991\",\n    \"totalItems\": 3,\n    \"orderedItems\": [\n        {\n          \"id\": \"https://lemmy.world/activities/like/aa6d9456-0960-4b9d15-561d1cef2991\",\n          \"actor\": \"https://lemmy.world/u/SpaceNo\",\n          \"object\": \"https://kbin.social/m/nostupidquestions@lemmy.world/t/433370/-/comment/2965\",\n          \"type\": \"Like\",\n          \"audience\": \"https://lemmy.world/c/nostupidquestions\"\n        },\n        {\n          \"id\": \"https://lemmy.world/activities/like/aa6d9456-094bf0-9d15-561d1cef299e\",\n          \"actor\": \"https://lemmy.world/u/Peter\",\n          \"object\": \"https://kbin.social/m/nostupidquestions@lemmy.world/t/433370/-/comment/2965\",\n          \"type\": \"Like\",\n          \"audience\": \"https://lemmy.world/c/nostupidquestions\"\n        },\n        {\n          \"id\": \"https://lemmy.world/activities/like/aa6d9456-0960-4bf0-9d15-56cef2994\",\n          \"actor\": \"https://lemmy.world/u/Rimu\",\n          \"object\": \"https://kbin.social/m/nostupidquestions@lemmy.world/t/433370/-/comment/2965\",\n          \"type\": \"Like\",\n          \"audience\": \"https://lemmy.world/c/nostupidquestions\"\n        }\n    ],\n  }\n  \"cc\": [\n    \"https://lemmy.world/c/nostupidquestions/followers\"\n  ],\n  \"type\": \"Announce\",\n  \"id\": \"https://lemmy.world/activities/announce/e49d182c-db32-4a10-bb-c57de890cd9e\",\n  \"@context\": [\n    ...\n  ],\n}\n
"},{"location":"fep/1a11/#references","title":"References","text":""},{"location":"fep/1a11/#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/1b12/","title":"FEP-1b12: Group federation","text":"Authors Status Date received Date final Tracking issue Discussions Repository Felix Ableitner me@nutomic.com FINAL 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.

"},{"location":"fep/1b12/#history","title":"History","text":"

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.

"},{"location":"fep/1b12/#following-a-group","title":"Following a Group","text":"

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.

"},{"location":"fep/1b12/#audience-property","title":"Audience property","text":"

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.

"},{"location":"fep/1b12/#threads-and-comments","title":"Threads and comments","text":"

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.

"},{"location":"fep/1b12/#the-announce-activity","title":"The Announce activity","text":"

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.

"},{"location":"fep/1b12/#group-moderation","title":"Group moderation","text":"

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.

"},{"location":"fep/1b12/#implementations","title":"Implementations","text":"

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.

"},{"location":"fep/1b12/#references","title":"References","text":""},{"location":"fep/1b12/#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/2100/","title":"FEP-2100: Unbound Group and Organization","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Diogo Peralta Cordeiro mail@diogo.site WITHDRAWN 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.

"},{"location":"fep/2100/#notation-and-definitions","title":"Notation and Definitions","text":"

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.

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 has gs:unbound = false","text":""},{"location":"fep/2100/#2-group-a-follows-group-b-which-has-gsunbound-true","title":"2. Group A follows Group B which has gs:unbound = true","text":""},{"location":"fep/2100/#3-group-a-follows-group-b-which-has-no-gsunbound-attribute","title":"3. Group A follows Group B which has no gs:unbound attribute","text":""},{"location":"fep/2100/#4-forwarding-from-inbox","title":"4. Forwarding from Inbox","text":""},{"location":"fep/2100/#references","title":"References","text":""},{"location":"fep/2100/#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/2277/","title":"FEP-2277: ActivityPub core types","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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:

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.

"},{"location":"fep/2277/#core-object-types","title":"Core object types","text":"

Activity Streams 2.0 standard defines 8 core object types:

Unfortunately, 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:

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:

  1. If object has inbox and outbox properties, return Actor.
  2. If object has publicKeyMultibase property, return VerificationMethod.
  3. If object has publicKeyPem property, return PublicKey.
  4. If object has href property, return Link.
  5. If object has actor property, return Activity.
  6. If object has items, orderedItems, totalItems, partOf, first, last, next, prev or current property, return Collection.
  7. Otherwise, return 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\".

"},{"location":"fep/2277/#json-ld","title":"JSON-LD","text":"

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.

"},{"location":"fep/2277/#references","title":"References","text":""},{"location":"fep/2277/#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/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.rocks DRAFT 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":""},{"location":"fep/22b6/#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/2677/","title":"FEP-2677: Identifying the Application Actor","text":"Authors Status Date received Tracking issue Discussions Repository Helge helge.krueger@gmail.com DRAFT 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.

"},{"location":"fep/2677/#requirements","title":"Requirements","text":""},{"location":"fep/2677/#application-actor","title":"Application Actor","text":"

We will first define, what we mean by an application actor. We will impose two requirements on them

  1. It's an [ActivityPub] Actor of type Application.
  2. It can be retrieved without authentication, e.g. with an unsigned HTTP request.

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:

  1. A fixed path for the application actor
  2. A fixed path for application information instead of aiming to attach it to the application actor.

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:

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.

"},{"location":"fep/2677/#currently-implemented-application-actors","title":"Currently implemented application actors","text":"Software Application Actor URI Bovine 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":""},{"location":"fep/2677/#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/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.com DRAFT 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.

"},{"location":"fep/268d/#background","title":"Background","text":"

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.

"},{"location":"fep/268d/#requirements","title":"Requirements","text":"

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 URI http://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":"FEP-2931: Representing context with a Collection","text":"Authors Status Type Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 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.

"},{"location":"fep/2931/#the-concept-of-a-canonical-context-collection","title":"The concept of a canonical context collection","text":"

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:

"},{"location":"fep/2931/#example-representation-of-a-context-collection","title":"Example representation of a context Collection","text":"

(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.

"},{"location":"fep/2931/#producing-and-maintaining-a-context-collection","title":"Producing and maintaining a context Collection","text":"

(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:

There 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\".

"},{"location":"fep/2931/#semantic-confusion-between-objects-and-collections-of-objects","title":"Semantic confusion between objects and collections of objects","text":"

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":""},{"location":"fep/2931/#references","title":"References","text":""},{"location":"fep/2931/#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/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.name DRAFT 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:

[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":""},{"location":"fep/2c59/#context","title":"Context","text":"

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.

"},{"location":"fep/2c59/#references","title":"References","text":""},{"location":"fep/2c59/#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/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.com WITHDRAWN 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.

"},{"location":"fep/2e40/#process-to-add-a-new-term-to-the-fep-vocabulary","title":"Process to add a new Term to the FEP Vocabulary","text":"

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].

  1. The $TERM MUST be indicative of its function. Avoid generic terms such as \"myProperty\" or \"foo\".
  2. If there are copyright, trademark, or any intellectual property rights concerns, the addition and use MUST be authorized in writing by the intellectual property rights holder under a F/RAND license. Examples include terms that use trademarked brand names, property names that utilize the titles of copyrighted works, and patented technology that would cause the use of the extension to require licensing a patent.
  3. Any addition MUST NOT create unreasonable legal, security, moral, or privacy issues that will result in direct harm to others. Examples of unacceptable additions include any containing racist language, technologies used to persecute minority populations, and unconsented pervasive tracking.

Furthermore, the author MAY perform the following action

  1. Add a new term of the form fep-xxxx-$TERM to /feps/assets/fep-2e40/namespace.json.
  2. Add a description for this term to /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.

"},{"location":"fep/2e40/#example-create-fep-term-eventsource","title":"Example \"Create FEP term: eventSource\"","text":"

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.

"},{"location":"fep/2e40/#acknowledgements","title":"Acknowledgements","text":"

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":""},{"location":"fep/2e40/#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/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":""},{"location":"fep/3264/","title":"FEP-3264: Federated Work Coordination","text":"Authors Status Type Date received Tracking issue Discussions Repository Lynn Foster lynn@mikorizal.org DRAFT implementation 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, with optional connected agreements, that constitute a body of scheduled work with defined deliverable(s).

The representation of a plan is a JSON document with the following properties:

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:

A Commitment is defined as A planned economic flow that has been scheduled or promised by an agent to another agent.

Example:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    {\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://project.example/resourcespecs/apple-picking\",\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://project.example/resourcespecs/apple-picking\",\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://project.example/resourcespecs/apple-picking\",\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://project.example/resourcespecs/apples\",\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://project.example/resourcespecs/apples\",\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://project.example/resourcespecs/cider-press\",\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://project.example/resourcespecs/manual-work\",\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://project.example/resourcespecs/manual-work\",\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://project.example/resourcespecs/apple-cider\",\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://project.example/resourcespecs/food-scraps\",\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:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    {\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\": \"vf:hasUnit\",\n      \"hasNumericalValue\": \"vf: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://project.example/resourcespecs/manual-work\",\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, by the backend software.

The representation of an economic event is a JSON document with the following properties:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    {\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\": \"vf:hasUnit\",\n      \"hasNumericalValue\": \"vf: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://project.example/resourcespecs/manual-work\",\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":""},{"location":"fep/3264/#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/34c1/","title":"FEP-34c1: Collection Filtering using TREE Hypermedia Vocabulary","text":"Authors Status Date received Tracking issue Discussions Repository Fred Hauschel @naturzukunft2026@mastodon.social DRAFT 2026-02-19 #763 Discussions codeberg"},{"location":"fep/34c1/#summary","title":"Summary","text":"

This FEP proposes using the TREE Hypermedia Vocabulary for client-initiated filter requests on ActivityPub Collections (especially Inbox). Clients can send a filter as a JSON-LD object via HTTP POST to retrieve a filtered subset of the collection.

This enables use cases such as: - Home Timeline: Content lifecycle activities (Create, Update, Delete, Announce) from followed actors, visible to public or followers - Mentions: Activities addressed to the actor via as:to or as:cc - Private Messages: Activities not addressed to as:Public (DMs, followers-only, group messages) - Media Filter: Only activities with images or videos

"},{"location":"fep/34c1/#context","title":"Context","text":"

The context document for this ActivityPub extension is at https://w3id.org/fep/34c1/context. Its contents are as follows:

{\n  \"@context\": {\n    \"fep34c1\": \"https://w3id.org/fep/34c1#\",\n    \"FilterRequest\": \"fep34c1:FilterRequest\",\n    \"pageSize\": {\n      \"@id\": \"fep34c1:pageSize\",\n      \"@type\": \"http://www.w3.org/2001/XMLSchema#nonNegativeInteger\"\n    },\n    \"myFollowees\": { \"@id\": \"fep34c1:myFollowees\", \"@type\": \"@id\" },\n    \"myAudience\": { \"@id\": \"fep34c1:myAudience\", \"@type\": \"@id\" }\n  }\n}\n
"},{"location":"fep/34c1/#defined-terms","title":"Defined Terms","text":""},{"location":"fep/34c1/#fep34c1filterrequest","title":"fep34c1:FilterRequest","text":"URI https://w3id.org/fep/34c1#FilterRequest Notes A container object for a filter query. It holds one or more tree:relation entries that define the filter criteria, and an optional fep34c1:pageSize to control pagination. Domain (none) Range (none)"},{"location":"fep/34c1/#fep34c1pagesize","title":"fep34c1:pageSize","text":"URI https://w3id.org/fep/34c1#pageSize Notes The maximum number of items per page in the filtered response. Domain fep34c1:FilterRequest Range xsd:nonNegativeInteger Functional Yes"},{"location":"fep/34c1/#fep34c1myfollowees","title":"fep34c1:myFollowees","text":"URI https://w3id.org/fep/34c1#myFollowees Notes Server-resolved placeholder. When used as tree:value, the server MUST replace it with the set of actor IRIs from the following collection of the authenticated (requesting) actor."},{"location":"fep/34c1/#fep34c1myaudience","title":"fep34c1:myAudience","text":"URI https://w3id.org/fep/34c1#myAudience Notes Server-resolved placeholder. When used as tree:value, the server MUST resolve it to all collection IRIs in which the authenticated actor is known to be a member. This includes at minimum the followers collections of followed actors, and MAY include group memberships, lists, or other collections as the server discovers them."},{"location":"fep/34c1/#motivation","title":"Motivation","text":""},{"location":"fep/34c1/#problem","title":"Problem","text":"

ActivityPub defines Collections (Inbox, Outbox, Followers, etc.) but no mechanism for clients to request filtered views. Clients must:

  1. Fetch all items with pagination
  2. Filter and sort locally
  3. Transfer unnecessarily large amounts of data

For a \"Home Timeline\" (activities from followees), this is particularly inefficient since the inbox also contains follow requests, likes, and other irrelevant activities.

"},{"location":"fep/34c1/#existing-approaches","title":"Existing Approaches","text":"Approach Problem FEP-5bf0 WITHDRAWN (June 2025); focused on server-side views, not client queries Hydra GET-oriented (URL templates), not for POST queries SPARQL Too complex for simple filters, security risks Proprietary Mastodon's /api/v1/timelines/home is not standardized"},{"location":"fep/34c1/#why-tree","title":"Why TREE?","text":"

The TREE Hypermedia Vocabulary offers:

"},{"location":"fep/34c1/#specification","title":"Specification","text":""},{"location":"fep/34c1/#1-filter-endpoint","title":"1. Filter Endpoint","text":"

Servers MAY provide a filter endpoint for collections:

POST /ap/actors/{username}/inbox/filter\nContent-Type: application/ld+json\n

The endpoint MUST be announced in the Collection object via the tree:search property:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/tree\"\n  ],\n  \"type\": \"OrderedCollection\",\n  \"id\": \"https://example.com/ap/actors/alice/inbox\",\n  \"tree:search\": {\n    \"@type\": \"fep34c1:FilterEndpoint\",\n    \"tree:template\": \"https://example.com/ap/actors/alice/inbox/filter\"\n  }\n}\n
"},{"location":"fep/34c1/#2-filter-request-format","title":"2. Filter Request Format","text":"

A filter request is a JSON-LD object of type fep34c1:FilterRequest:

{\n  \"@context\": {\n    \"tree\": \"https://w3id.org/tree#\",\n    \"fep34c1\": \"https://w3id.org/fep/34c1#\",\n    \"as\": \"https://www.w3.org/ns/activitystreams#\",\n    \"rdf\": \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n  },\n  \"@type\": \"fep34c1:FilterRequest\",\n  \"tree:relation\": [\n    {\n      \"@type\": \"tree:EqualToRelation\",\n      \"tree:path\": { \"@id\": \"rdf:type\" },\n      \"tree:value\": [\n        { \"@id\": \"as:Create\" },\n        { \"@id\": \"as:Update\" },\n        { \"@id\": \"as:Delete\" },\n        { \"@id\": \"as:Announce\" }\n      ]\n    },\n    {\n      \"@type\": \"tree:EqualToRelation\",\n      \"tree:path\": { \"@id\": \"as:actor\" },\n      \"tree:value\": { \"@id\": \"fep34c1:myFollowees\" }\n    },\n    {\n      \"@type\": \"tree:EqualToRelation\",\n      \"tree:path\": [{ \"@id\": \"as:to\" }, { \"@id\": \"as:cc\" }],\n      \"tree:value\": [\n        { \"@id\": \"https://www.w3.org/ns/activitystreams#Public\" },\n        { \"@id\": \"fep34c1:myAudience\" }\n      ]\n    }\n  ],\n  \"fep34c1:pageSize\": 20\n}\n
"},{"location":"fep/34c1/#3-relation-types","title":"3. Relation Types","text":""},{"location":"fep/34c1/#31-must-be-supported-core","title":"3.1 MUST be Supported (Core)","text":"Relation Semantics SPARQL Equivalent tree:EqualToRelation Value is equal to ?x = ?value tree:NotEqualToRelation Value is not equal to ?x != ?value tree:GreaterThanRelation Value is greater than ?x > ?value tree:LessThanRelation Value is less than ?x < ?value tree:GreaterThanOrEqualToRelation Value is greater than or equal to ?x >= ?value tree:LessThanOrEqualToRelation Value is less than or equal to ?x <= ?value"},{"location":"fep/34c1/#32-should-be-supported-recommended","title":"3.2 SHOULD be Supported (Recommended)","text":"Relation Semantics SPARQL Equivalent tree:PrefixRelation Value starts with STRSTARTS(?x, ?value) tree:SubstringRelation Value contains CONTAINS(?x, ?value) tree:SuffixRelation Value ends with STRENDS(?x, ?value)"},{"location":"fep/34c1/#4-special-values","title":"4. Special Values","text":""},{"location":"fep/34c1/#41-fep34c1myfollowees-dynamic-followee-list","title":"4.1 fep34c1:myFollowees - Dynamic Followee List","text":"

The value fep34c1:myFollowees is a server-resolved placeholder. When used as tree:value, the server MUST replace it with the set of actor IRIs from the following collection of the authenticated (requesting) actor.

This means a relation like:

{\n  \"@type\": \"tree:EqualToRelation\",\n  \"tree:path\": { \"@id\": \"as:actor\" },\n  \"tree:value\": { \"@id\": \"fep34c1:myFollowees\" }\n}\n

is equivalent to: \"Return only activities where the as:actor property matches one of the actor IRIs in the requesting actor's following collection.\" In other words, only activities authored by actors that the requesting user follows will be included in the result.

"},{"location":"fep/34c1/#42-fep34c1myaudience-dynamic-audience-collections","title":"4.2 fep34c1:myAudience - Dynamic Audience Collections","text":"

The value fep34c1:myAudience is a server-resolved placeholder. When used as tree:value, the server MUST resolve it to all collection IRIs in which the authenticated actor is known to be a member. This includes at minimum the followers collections of followed actors, and MAY include group memberships, lists, or other collections.

This is useful for audience filtering: Fediverse servers typically address followers-only posts with the actor's followers collection IRI in as:to or as:cc.

Note: These are explicit FEP-34c1 placeholder properties rather than reused ActivityStreams terms. An earlier version of this spec used as:following and as:followers as placeholders, but this was semantically incorrect \u2014 as:followers denotes the collection itself, not its items (see discussion). Using dedicated FEP-34c1 properties makes the server-resolved semantics explicit and avoids overloading AS2 vocabulary. An alternative approach using SHACL Property Paths was considered but rejected due to the complexity of expressing pagination traversal in SHACL path expressions.

"},{"location":"fep/34c1/#43-multiple-values-or-conjunction","title":"4.3 Multiple Values (OR Conjunction)","text":"

If tree:value is an array, the values are combined with OR:

{\n  \"tree:value\": [\n    { \"@id\": \"as:Create\" },\n    { \"@id\": \"as:Update\" },\n    { \"@id\": \"as:Delete\" },\n    { \"@id\": \"as:Announce\" }\n  ]\n}\n

Semantics: rdf:type = as:Create OR rdf:type = as:Update OR rdf:type = as:Delete OR rdf:type = as:Announce

"},{"location":"fep/34c1/#5-multiple-paths-union","title":"5. Multiple Paths (UNION)","text":"

If tree:path is an array, the filter matches if any of the specified paths contains a matching value. This is equivalent to a UNION in SPARQL.

{\n  \"@type\": \"tree:EqualToRelation\",\n  \"tree:path\": [{ \"@id\": \"as:to\" }, { \"@id\": \"as:cc\" }],\n  \"tree:value\": { \"@id\": \"https://example.com/ap/actors/alice\" }\n}\n

Semantics: \"Match if the value appears in as:to OR in as:cc.\"

This is particularly useful for audience filtering, since ActivityPub activities use both as:to and as:cc for addressing.

"},{"location":"fep/34c1/#6-combination-of-relations-and-conjunction","title":"6. Combination of Relations (AND Conjunction)","text":"

Multiple tree:relation entries are combined with AND:

{\n  \"tree:relation\": [\n    { \"@type\": \"tree:EqualToRelation\", \"tree:path\": {\"@id\": \"rdf:type\"}, \"tree:value\": {\"@id\": \"as:Create\"} },\n    { \"@type\": \"tree:EqualToRelation\", \"tree:path\": {\"@id\": \"as:actor\"}, \"tree:value\": {\"@id\": \"fep34c1:myFollowees\"} },\n    { \"@type\": \"tree:EqualToRelation\", \"tree:path\": [{\"@id\": \"as:to\"}, {\"@id\": \"as:cc\"}], \"tree:value\": {\"@id\": \"https://www.w3.org/ns/activitystreams#Public\"} }\n  ]\n}\n

Semantics: rdf:type = as:Create AND as:actor IN myFollowees AND (as:to = as:Public OR as:cc = as:Public)

"},{"location":"fep/34c1/#7-response-format","title":"7. Response Format","text":"

The response is an OrderedCollectionPage with the filtered items. Pagination MUST use as:next and as:prev (standard ActivityStreams pagination). Servers MAY additionally include tree:relation entries to provide boundary values for TREE-aware clients.

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/tree\",\n    { \"fep34c1\": \"https://w3id.org/fep/34c1#\" }\n  ],\n  \"@type\": \"OrderedCollectionPage\",\n  \"partOf\": \"https://example.com/ap/actors/alice/inbox\",\n  \"next\": \"https://example.com/ap/actors/alice/inbox/filter?cursor=abc123\",\n  \"prev\": \"https://example.com/ap/actors/alice/inbox/filter?cursor=def456\",\n  \"orderedItems\": [\n    { /* Activity 1 */ },\n    { /* Activity 2 */ }\n  ],\n  \"tree:relation\": [\n    {\n      \"@type\": \"tree:GreaterThanRelation\",\n      \"tree:path\": { \"@id\": \"as:published\" },\n      \"tree:value\": \"2026-01-28T10:00:00Z\",\n      \"tree:node\": \"https://example.com/ap/actors/alice/inbox/filter?cursor=abc123\"\n    }\n  ],\n  \"totalItems\": 42\n}\n
"},{"location":"fep/34c1/#8-pagination","title":"8. Pagination","text":"

Servers MUST provide as:next and as:prev links on OrderedCollectionPage responses for standard ActivityStreams pagination. This ensures compatibility with existing ActivityPub clients.

Servers MAY additionally include tree:relation entries to provide boundary values for TREE-aware clients. Each pagination relation uses three properties from the TREE vocabulary:

For example, a tree:GreaterThanRelation with tree:value of \"2026-01-28T10:00:00Z\" and a tree:node URL means: \"Fetching tree:node will return items with as:published greater than 2026-01-28T10:00:00Z.\"

Pagination directions:

"},{"location":"fep/34c1/#9-authentication","title":"9. Authentication","text":"

The filter endpoint MUST require the same authentication as the collection itself:

"},{"location":"fep/34c1/#security-considerations","title":"Security Considerations","text":""},{"location":"fep/34c1/#query-injection","title":"Query Injection","text":"

Servers MUST validate and sanitize filter requests. In particular:

Recommended allowed tree:path values:

"},{"location":"fep/34c1/#rate-limiting","title":"Rate Limiting","text":"

Filter requests are potentially more expensive than regular GET requests. Servers SHOULD implement rate limiting.

"},{"location":"fep/34c1/#no-arbitrary-sparql-queries","title":"No Arbitrary SPARQL Queries","text":"

This FEP explicitly defines no SPARQL interface. The supported operations are limited to the defined tree:Relation types.

"},{"location":"fep/34c1/#examples","title":"Examples","text":""},{"location":"fep/34c1/#example-1-home-timeline","title":"Example 1: Home Timeline","text":"

Shows content lifecycle activities from followed actors, visible to public or followers.

{\n  \"@context\": {\n    \"tree\": \"https://w3id.org/tree#\",\n    \"fep34c1\": \"https://w3id.org/fep/34c1#\",\n    \"as\": \"https://www.w3.org/ns/activitystreams#\",\n    \"rdf\": \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n  },\n  \"@type\": \"fep34c1:FilterRequest\",\n  \"tree:relation\": [\n    {\n      \"@type\": \"tree:EqualToRelation\",\n      \"tree:path\": { \"@id\": \"rdf:type\" },\n      \"tree:value\": [\n        { \"@id\": \"as:Create\" },\n        { \"@id\": \"as:Update\" },\n        { \"@id\": \"as:Delete\" },\n        { \"@id\": \"as:Announce\" }\n      ]\n    },\n    {\n      \"@type\": \"tree:EqualToRelation\",\n      \"tree:path\": { \"@id\": \"as:actor\" },\n      \"tree:value\": { \"@id\": \"fep34c1:myFollowees\" }\n    },\n    {\n      \"@type\": \"tree:EqualToRelation\",\n      \"tree:path\": [{ \"@id\": \"as:to\" }, { \"@id\": \"as:cc\" }],\n      \"tree:value\": [\n        { \"@id\": \"https://www.w3.org/ns/activitystreams#Public\" },\n        { \"@id\": \"fep34c1:myAudience\" }\n      ]\n    }\n  ],\n  \"fep34c1:pageSize\": 20\n}\n
"},{"location":"fep/34c1/#example-2-mentions","title":"Example 2: Mentions","text":"

Shows activities where the actor is directly addressed via as:to or as:cc (public mentions only, excluding DMs).

{\n  \"@context\": {\n    \"tree\": \"https://w3id.org/tree#\",\n    \"fep34c1\": \"https://w3id.org/fep/34c1#\",\n    \"as\": \"https://www.w3.org/ns/activitystreams#\",\n    \"rdf\": \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n  },\n  \"@type\": \"fep34c1:FilterRequest\",\n  \"tree:relation\": [\n    {\n      \"@type\": \"tree:EqualToRelation\",\n      \"tree:path\": { \"@id\": \"rdf:type\" },\n      \"tree:value\": [{ \"@id\": \"as:Create\" }]\n    },\n    {\n      \"@type\": \"tree:EqualToRelation\",\n      \"tree:path\": [{ \"@id\": \"as:to\" }, { \"@id\": \"as:cc\" }],\n      \"tree:value\": { \"@id\": \"https://example.com/ap/actors/alice\" }\n    },\n    {\n      \"@type\": \"tree:EqualToRelation\",\n      \"tree:path\": [{ \"@id\": \"as:to\" }, { \"@id\": \"as:cc\" }],\n      \"tree:value\": { \"@id\": \"https://www.w3.org/ns/activitystreams#Public\" }\n    }\n  ]\n}\n
"},{"location":"fep/34c1/#example-3-private-messages","title":"Example 3: Private Messages","text":"

Shows activities not addressed to as:Public (neither in as:to nor as:cc). This includes direct messages, followers-only posts, and group messages.

{\n  \"@context\": {\n    \"tree\": \"https://w3id.org/tree#\",\n    \"fep34c1\": \"https://w3id.org/fep/34c1#\",\n    \"as\": \"https://www.w3.org/ns/activitystreams#\",\n    \"rdf\": \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n  },\n  \"@type\": \"fep34c1:FilterRequest\",\n  \"tree:relation\": [\n    {\n      \"@type\": \"tree:EqualToRelation\",\n      \"tree:path\": { \"@id\": \"rdf:type\" },\n      \"tree:value\": [{ \"@id\": \"as:Create\" }]\n    },\n    {\n      \"@type\": \"tree:NotEqualToRelation\",\n      \"tree:path\": { \"@id\": \"as:to\" },\n      \"tree:value\": { \"@id\": \"https://www.w3.org/ns/activitystreams#Public\" }\n    },\n    {\n      \"@type\": \"tree:NotEqualToRelation\",\n      \"tree:path\": { \"@id\": \"as:cc\" },\n      \"tree:value\": { \"@id\": \"https://www.w3.org/ns/activitystreams#Public\" }\n    }\n  ]\n}\n
"},{"location":"fep/34c1/#example-4-activities-since-timestamp","title":"Example 4: Activities Since Timestamp","text":"
{\n  \"@context\": {\n    \"tree\": \"https://w3id.org/tree#\",\n    \"fep34c1\": \"https://w3id.org/fep/34c1#\",\n    \"as\": \"https://www.w3.org/ns/activitystreams#\",\n    \"xsd\": \"http://www.w3.org/2001/XMLSchema#\"\n  },\n  \"@type\": \"fep34c1:FilterRequest\",\n  \"tree:relation\": [\n    {\n      \"@type\": \"tree:GreaterThanRelation\",\n      \"tree:path\": { \"@id\": \"as:published\" },\n      \"tree:value\": {\n        \"@value\": \"2026-01-27T00:00:00Z\",\n        \"@type\": \"xsd:dateTime\"\n      }\n    }\n  ]\n}\n
"},{"location":"fep/34c1/#example-5-posts-from-a-specific-instance","title":"Example 5: Posts from a Specific Instance","text":"
{\n  \"@context\": {\n    \"tree\": \"https://w3id.org/tree#\",\n    \"fep34c1\": \"https://w3id.org/fep/34c1#\",\n    \"as\": \"https://www.w3.org/ns/activitystreams#\"\n  },\n  \"@type\": \"fep34c1:FilterRequest\",\n  \"tree:relation\": [\n    {\n      \"@type\": \"tree:PrefixRelation\",\n      \"tree:path\": { \"@id\": \"as:actor\" },\n      \"tree:value\": \"https://mastodon.social/\"\n    }\n  ]\n}\n
"},{"location":"fep/34c1/#implementation-notes","title":"Implementation Notes","text":""},{"location":"fep/34c1/#for-servers","title":"For Servers","text":"
  1. Special placeholder values (fep34c1:myFollowees, fep34c1:myAudience) MUST be resolved at query time before evaluating the filter (see Section 4).

  2. Allowed tree:path values SHOULD be restricted to a known set of ActivityStreams properties (see Security Considerations).

  3. For multi-path with EqualToRelation, the semantics are UNION (match if any path matches). For NotEqualToRelation, the semantics are ALL (none of the paths may match).

"},{"location":"fep/34c1/#for-clients","title":"For Clients","text":"
  1. Clients SHOULD check if tree:search is present in the collection
  2. If not present: fall back to client-side filtering
  3. Filter requests SHOULD be cached (ETag/If-None-Match)
  4. Clients SHOULD expect 405 Method Not Allowed if the server does not support filters
"},{"location":"fep/34c1/#backwards-compatibility","title":"Backwards Compatibility","text":"

This FEP is fully backwards compatible:

"},{"location":"fep/34c1/#implementations","title":"Implementations","text":""},{"location":"fep/34c1/#references","title":"References","text":""},{"location":"fep/34c1/#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/34ec/","title":"FEP-34ec: Notification Collection Endpoint","text":"Authors Status Date received Tracking issue Discussions Repository Fred Hauschel @naturzukunft2026@mastodon.social DRAFT 2026-03-15 #782 Discussions codeberg"},{"location":"fep/34ec/#summary","title":"Summary","text":"

This FEP defines a standardized notification collection for ActivityPub actors. A new notifications property under endpoints (ActivityPub \u00a75.7) provides an OrderedCollection containing references to activities that the server deems notification-worthy. Unlike the inbox, which serves as the delivery channel for all incoming activities, the notification collection is a curated subset \u2014 engagement-oriented events such as likes, mentions, and boosts. Dismissal of notifications uses the standard Remove activity (ActivityPub \u00a77.5). Batch dismissal is supported via FEP-db70 (RemoveAll) with optional FEP-34c1 filtering.

"},{"location":"fep/34ec/#motivation","title":"Motivation","text":"

ActivityPub defines an inbox as the primary collection for incoming activities. However, clients need to distinguish between content-oriented activities (home feed) and engagement-oriented events (notifications) \u2014 a separation that SWICG #21 has long called for.

Today, every Fediverse software (Mastodon, Pleroma, GoToSocial, Misskey) implements notifications as a proprietary REST API without interoperability. There is no standardized ActivityPub endpoint for notifications.

This FEP closes this gap by defining: - A standardized endpoint under endpoints.notifications - A server-curated OrderedCollection of notification-worthy activities - Dismissal via standard Remove (ActivityPub \u00a77.5), batch dismissal via FEP-db70 - Optional filtering via FEP-34c1

No new types or vocabulary beyond the notifications endpoint property are introduced. The collection holds references to existing activities \u2014 the activity types themselves provide categorization.

"},{"location":"fep/34ec/#specification","title":"Specification","text":""},{"location":"fep/34ec/#1-notification-endpoint-on-the-actor","title":"1. Notification Endpoint on the Actor","text":"

A conforming server MUST provide the notifications property under endpoints in the actor object:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/34ec\"\n  ],\n  \"type\": \"Person\",\n  \"id\": \"https://example.com/actors/bob\",\n  \"endpoints\": {\n    \"sharedInbox\": \"https://example.com/inbox\",\n    \"notifications\": \"https://example.com/actors/bob/notifications\"\n  }\n}\n

The notifications property points to an OrderedCollection sorted by server receive time in descending order (newest first).

"},{"location":"fep/34ec/#2-collection-contents","title":"2. Collection Contents","text":"

The notification collection contains references to activities that the server considers notification-worthy for the actor. These are typically engagement-oriented events:

Activity Type Typical Condition Like Object is owned by the actor Announce Object is owned by the actor Create Actor is mentioned (to/cc/tag) Follow Actor is the follow target Update Object is owned by or observed by the actor

This table is non-normative. Servers MAY include any activity type and SHOULD apply their own criteria for what constitutes a notification.

The activities referenced in the notification collection also remain in the actor's inbox. The notification collection is a view, not a separate delivery mechanism.

"},{"location":"fep/34ec/#3-collection-semantics","title":"3. Collection Semantics","text":"
{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/34ec\"\n  ],\n  \"type\": \"OrderedCollection\",\n  \"id\": \"https://example.com/actors/bob/notifications\",\n  \"totalItems\": 3,\n  \"orderedItems\": [\n    \"https://alice.example/activities/like-123\",\n    \"https://carol.example/activities/create-456\",\n    \"https://dave.example/activities/announce-789\"\n  ]\n}\n

Servers MAY inline the full activity objects instead of providing only IRIs. When inlining, the standard Activity Streams representation is used \u2014 no wrapper type is needed:

{\n  \"type\": \"OrderedCollection\",\n  \"id\": \"https://example.com/actors/bob/notifications\",\n  \"totalItems\": 2,\n  \"orderedItems\": [\n    {\n      \"type\": \"Like\",\n      \"id\": \"https://alice.example/activities/like-123\",\n      \"actor\": \"https://alice.example/actors/alice\",\n      \"object\": \"https://example.com/posts/post-1\",\n      \"published\": \"2026-02-24T10:00:00Z\"\n    },\n    {\n      \"type\": \"Create\",\n      \"id\": \"https://carol.example/activities/create-456\",\n      \"actor\": \"https://carol.example/actors/carol\",\n      \"object\": {\n        \"type\": \"Note\",\n        \"content\": \"Hey @bob, check this out!\"\n      },\n      \"published\": \"2026-02-24T09:30:00Z\"\n    }\n  ]\n}\n
"},{"location":"fep/34ec/#4-pagination","title":"4. Pagination","text":"

The collection SHOULD support OrderedCollectionPage pagination. Since the collection only contains pending notifications, it typically remains small. Pagination becomes relevant only with larger volumes.

{\n  \"type\": \"OrderedCollection\",\n  \"id\": \"https://example.com/actors/bob/notifications\",\n  \"totalItems\": 150,\n  \"first\": \"https://example.com/actors/bob/notifications?page=1\"\n}\n
"},{"location":"fep/34ec/#5-c2s-operations","title":"5. C2S Operations","text":""},{"location":"fep/34ec/#51-dismiss-a-single-notification-remove","title":"5.1 Dismiss a Single Notification (Remove)","text":"

A client dismisses a notification by posting a Remove activity to the actor's outbox, as defined in ActivityPub \u00a76.11:

{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"type\": \"Remove\",\n  \"actor\": \"https://example.com/actors/bob\",\n  \"object\": \"https://alice.example/activities/like-123\",\n  \"target\": \"https://example.com/actors/bob/notifications\"\n}\n

The server MUST remove the activity reference from the notification collection. The activity itself MUST NOT be deleted from the inbox.

"},{"location":"fep/34ec/#52-batch-dismiss-removeall","title":"5.2 Batch Dismiss (RemoveAll)","text":"

Batch dismissal is supported via FEP-db70 (RemoveAll). An optional FEP-34c1 filter can be provided to dismiss only matching notifications.

Dismiss all notifications (\"mark all as read\"):

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/db70\"\n  ],\n  \"type\": \"RemoveAll\",\n  \"actor\": \"https://example.com/actors/bob\",\n  \"target\": \"https://example.com/actors/bob/notifications\"\n}\n

Dismiss all notifications of a specific type (with FEP-34c1 filter):

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/db70\",\n    \"https://w3id.org/fep/34c1\",\n    \"https://w3id.org/tree\"\n  ],\n  \"type\": \"RemoveAll\",\n  \"actor\": \"https://example.com/actors/bob\",\n  \"target\": \"https://example.com/actors/bob/notifications\",\n  \"filter\": {\n    \"type\": \"FilterRequest\",\n    \"relation\": [\n      {\n        \"type\": \"EqualToRelation\",\n        \"path\": { \"@id\": \"as:type\" },\n        \"value\": { \"@id\": \"as:Like\" }\n      }\n    ]\n  }\n}\n

Dismiss all notifications older than a given date:

{\n  \"type\": \"RemoveAll\",\n  \"actor\": \"https://example.com/actors/bob\",\n  \"target\": \"https://example.com/actors/bob/notifications\",\n  \"filter\": {\n    \"type\": \"FilterRequest\",\n    \"relation\": [\n      {\n        \"type\": \"LessThanRelation\",\n        \"path\": { \"@id\": \"as:published\" },\n        \"value\": { \"@value\": \"2026-02-17T00:00:00Z\", \"@type\": \"xsd:dateTime\" }\n      }\n    ]\n  }\n}\n

The server MUST remove all activity references matching the filter from the notification collection. Without a filter, the server MUST remove all activity references.

"},{"location":"fep/34ec/#6-collection-filtering-read","title":"6. Collection Filtering (Read)","text":"

Conforming servers SHOULD support FEP-34c1 filtering for the notification collection. The activity type SHOULD be accepted as a tree:path for filtering.

The server signals filter support via tree:search in the collection:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/34ec\",\n    \"https://w3id.org/fep/34c1\",\n    \"https://w3id.org/tree\"\n  ],\n  \"type\": \"OrderedCollection\",\n  \"id\": \"https://example.com/actors/bob/notifications\",\n  \"totalItems\": 25,\n  \"tree:search\": {\n    \"type\": \"FilterEndpoint\",\n    \"template\": \"https://example.com/actors/bob/notifications/filter\"\n  }\n}\n

A client retrieving only Like notifications sends a POST to the filter endpoint:

Request: POST https://example.com/actors/bob/notifications/filter

{\n  \"@context\": [\n    \"https://w3id.org/fep/34c1\",\n    \"https://w3id.org/tree\"\n  ],\n  \"type\": \"FilterRequest\",\n  \"relation\": [\n    {\n      \"type\": \"EqualToRelation\",\n      \"path\": { \"@id\": \"as:type\" },\n      \"value\": { \"@id\": \"as:Like\" }\n    }\n  ],\n  \"pageSize\": 20\n}\n

Response:

{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"type\": \"OrderedCollectionPage\",\n  \"partOf\": \"https://example.com/actors/bob/notifications\",\n  \"totalItems\": 5,\n  \"orderedItems\": [\n    {\n      \"type\": \"Like\",\n      \"id\": \"https://alice.example/activities/like-123\",\n      \"actor\": \"https://alice.example/actors/alice\",\n      \"object\": \"https://example.com/posts/post-1\",\n      \"published\": \"2026-02-24T10:00:00Z\"\n    }\n  ]\n}\n
"},{"location":"fep/34ec/#7-authorization","title":"7. Authorization","text":"

The notification collection MUST only be accessible to the authenticated actor. Unauthenticated requests MUST be rejected with 401 Unauthorized.

"},{"location":"fep/34ec/#security-considerations","title":"Security Considerations","text":""},{"location":"fep/34ec/#conformance","title":"Conformance","text":"

A conforming server MUST: - Provide notifications under endpoints in the actor object - Populate the notification collection with references to notification-worthy activities - Process Remove activities targeting the notification collection by removing the reference - NOT delete the underlying activity from the inbox when removing from the notification collection

A conforming server SHOULD: - Support OrderedCollectionPage pagination for larger volumes - Support FEP-34c1 filtering for the notification collection - Accept activity type as a tree:path in filters - Support FEP-db70 RemoveAll for batch dismissal

"},{"location":"fep/34ec/#implementations","title":"Implementations","text":""},{"location":"fep/34ec/#references","title":"References","text":""},{"location":"fep/34ec/#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/35b7/","title":"FEP-35b7: Fediverse Servers, Instances, and Tenants","text":"Authors Status Type Date received Tracking issue Discussions Repository Steve Bate svc-fep@stevebate.net DRAFT informational 2026-04-22 #832 Discussions codeberg"},{"location":"fep/35b7/#summary","title":"Summary","text":"

This proposal defines terminology related to servers, instances, and tenants in a Fediverse context. The goal is to provide consistent vocabulary for specifications, implementations, and documentation across projects.

"},{"location":"fep/35b7/#motivation","title":"Motivation","text":"

Fediverse discussions use overlapping but sometimes inconsistent terminology when describing software, deployments, and administrative boundaries. In particular, the words \u201cserver\u201d and \u201cinstance\u201d are often used interchangeably.

The terminology issue becomes even more clear for multi-tenant server implementations and instances. As more projects implement multi-tenant architectures, clear terminology becomes important for:

This document proposes terminology that other FEPs and implementations may reference.

"},{"location":"fep/35b7/#definitions","title":"Definitions","text":""},{"location":"fep/35b7/#server-implementation","title":"Server implementation","text":"

A server implementation is a software codebase that implements one or more Fediverse-related protocols (such as ActivityPub). This software must be deployed on computational infrastructure to participate in the Fediverse.

Characteristics:

Example Implementations:

The word server may also refer to computational infrastructure (physical or virtual hardware, containers, etc.) in some contexts. This is not how the word is used in this proposal, which only refers to server software.

"},{"location":"fep/35b7/#server-instance","title":"Server instance","text":"

A server instance is a specific deployment of a server implementation, including its running processes, configuration, and associated persistent storage, operated by a defined administrative authority.

Characteristics:

Examples:

In common Fediverse usage, \u201cinstance\u201d often implicitly refers to a server instance; this document uses the explicit term \u201cserver instance\u201d for clarity.

"},{"location":"fep/35b7/#tenant","title":"Tenant","text":"

A tenant in a Fediverse context is a logically and administratively distinct group of actors and related data that is served by a server instance and for which the server provides isolated configuration, policies, and data separation.

This definition adapts the general notion of multitenancy where a single software instance serves multiple tenants while isolating their data and configuration to the specific case of Fediverse services.

Characteristics:

Examples:

Related concepts:

"},{"location":"fep/35b7/#relationship-between-terms","title":"Relationship Between Terms","text":"
+-----------------------+\n| Server implementation |\n+-----------------------+\n| software codebase     |\n| release artifacts     |\n+-----------------------+\n         |\n         | 0..*\n         v\n+-----------------------+\n|    Server instance    |\n+-----------------------+\n| running deployment    |\n| config + storage      |\n| admin boundary        |\n+-----------------------+\n    |                 \\\n    |                  \\\n    |                   \\\n    v                    v\n+----------------+  +----------------+\n| Single tenant  |  | Multi-tenant   |\n| deployment     |  | deployment     |\n+----------------+  +----------------+\n     \\              /\n      \\            /\n       \\          /\n        \\ 1      / 1..*\n         v      v\n       +----------------+\n       |     Tenant     |\n       +----------------+\n       | isolated data  |\n       | isolated policy|\n       | admin boundary |\n       +----------------+\n
"},{"location":"fep/35b7/#scope-and-non-goals","title":"Scope and Non-Goals","text":"

This document:

"},{"location":"fep/35b7/#references","title":"References","text":""},{"location":"fep/35b7/#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/37f2/","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/@bengo DRAFT 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:

"},{"location":"fep/37f2/#w3c-community-group-process","title":"W3C Community Group Process","text":"

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":""},{"location":"fep/37f2/#editorial-notes","title":"Editorial Notes","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/3ab2/","title":"FEP-3ab2: ActivityPub Event Streaming API","text":"Authors Status Date received Tracking issue Discussions Repository Steve Bate svc-fep@stevebate.net DRAFT 2026-03-14 #778 Discussions codeberg"},{"location":"fep/3ab2/#motivation","title":"Motivation","text":"

The ActivityPub specification defines a server-to-server federation protocol but does not specify how a client application (e.g. a Web front-end or mobile app) receives real-time notifications from an ActivityPub server. This FEP proposes a straightforward, standards-aligned approach using HTTP/1.1 Server-Sent Events.

Goals: - Reuse existing HTTP authentication. - Efficiently use SSE streaming resources - Provide a discoverable, RESTful control API for managing SSE sessions.

"},{"location":"fep/3ab2/#summary","title":"Summary","text":"

This FEP specifies a lightweight, cookie-authenticated Server-Sent Events (SSE) streaming API that ActivityPub server implementations can expose to their authenticated clients. The API provides:

  1. A session-control sub-API for issuing and revoking short-lived, singoe-use streaming tickets, and for managing per-user topic subscriptions.
  2. A stream endpoint that delivers a multiplexed, real-time event feed for all topics the authenticated to which the user is subscribed.

The design intentionally separates authentication (handled by the server's existing mechanism, e.g. OAuth 2.0, session cookies, or HTTP Basic) from streaming authorization (a short-lived ticket stored in an HttpOnly cookie), so that the SSE connection never carries user credentials.

This proposal addresses two issues related to SSE event streaming in an ActivityPub context.

"},{"location":"fep/3ab2/#terminology","title":"Terminology","text":"Term Definition Actor A standard ActivityPub actor Principal The authenticated identity (URI of the Actor) making a request. Ticket A short-lived, randomly-generated opaque token that authorizes one SSE stream connection. Topic An opaque string naming a channel of events (e.g. notifications, inbox, timeline:home). SSE Session The lifecycle from ticket issuance through stream connection to ticket revocation or expiry.

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/3ab2/#endpoint-discovery","title":"Endpoint Discovery","text":"

The streaming control endpoint is exposed using the ActivityPub Actor endpoints property using the streamingControl endpoint property.

{\n  \"@context\": [\n    \"https://w3id.org/fep/3ab2\",\n    \"https://www.w3.org/ns/activitystreams\"\n  ],\n  \"type\": \"Person\",\n  \"id\": \"https://server.example/actor/1\",\n  ...\n  \"endpoints\": [\n    \"streamingControl\": \"https://server.example/streaming/control\"\n  ]\n}\n
"},{"location":"fep/3ab2/#topics","title":"Topics","text":"

Topic represented as multiple text segments separated by the \"/\" character.

"},{"location":"fep/3ab2/#topic-wildcards","title":"Topic Wildcards","text":"

For subscriptions, topics MAY contain wildcards if the server supports them. The standard MQTT wildcards are used for this purpose. A server can advertise wildcard support in the Create Session response via the wildcard_support field.

Wildcard Description Example + Matches a single topic segment foo/+/baz # Matches zero or more segments at the end of the pattern foo/#

When publishing, the topic MUST use URL escaping for any wildcard characters in the topic. Subscribers MUST also escape non-wildcard '+' and '#' characters in topic subscriptions.

"},{"location":"fep/3ab2/#activitypub-topics","title":"ActivityPub topics","text":"

For ActivityPub entities, topics will correspond to ActivityPub HTTP/HTTPS URIs. To construct a topic from a URI, the following algorithm is used.

  1. Remove the URL scheme.
  2. The URL authority (domain and optional port) is the first segment of the topic.
  3. The URL path becomes \"/\"-separated path segments
  4. If a URL fragment is present this becomes the final segment.

Topic wildcard characters in topic segments MUST be URL-escaped.

Examples

URL Topic https://server.example/note/1 server.example/note/1 https://server.example:1000/actor#xyz server.example:1000/actor/xyz https://server.example:1000/actor+123#xyz server.example:1000/actor%2B123/xyz"},{"location":"fep/3ab2/#session-management","title":"Session Management","text":""},{"location":"fep/3ab2/#create-session-post-streamingcontrol","title":"Create Session \u2014 POST {streamingControl}","text":"

Requires: authenticated Principal.

The server:

  1. Generates a cryptographically random ticket.
  2. Records SSE session state (ticket, Principal, origin, etc.)
  3. Sets a Set-Cookie response header carrying the ticket value with the configured path, SameSite, Secure, and HttpOnly attributes.
  4. Returns 201 Created with a JSON body.

Server Response body:

Field Type Description subscriptions_url string Required. Absolute URL of the subscriptions endpoint. Clients MUST use this value for all subscription management requests in this session. stream_url string Required. Absolute URL of the SSE stream endpoint. Clients MUST use this value to open the event stream. expires_at string ISO 8601 UTC timestamp of ticket expiry wildcard_support bool Indicates whether the server supports topic wildcards or not. Defaults is true if not provided.

Servers MUST include subscriptions_url and stream_url in every successful 201 Created response to this endpoint. Clients MUST treat these URLs as opaque and MUST NOT attempt to derive or construct them from the SSE prefix or any other path convention.

Example

{\n  \"subscriptions_url\": \"https://server.example/sse/control/subscriptions\",\n  \"stream_url\": \"https://server.example/sse/stream\",\n  \"expires_at\": \"2001-03-10T12:05:00Z\",\n  \"wildcard_support\": true\n}\n

Cookie attributes (RECOMMENDED defaults):

Attribute Recommended value Path Scoped to the implementation's streaming API path SameSite Lax Secure true in production HttpOnly Server MAY set to false to allow JavaScript access Max-Age Cookie expiry age

The ticket MUST expire after a server-configured TTL. Clients MUST call POST /control again to renew before opening a new stream.

"},{"location":"fep/3ab2/#revoke-session-delete-streamingcontrol","title":"Revoke Session \u2014 DELETE {streamingControl}","text":"

Requires: authenticated Principal. The ticket cookie is read if present.

The server: 1. Invalidates the ticket. 2. Sends a Set-Cookie header that clears the cookie (Max-Age=0). 3. Returns 204 No Content if successfull.

Client implementations SHOULD also close any active SSE connections associated with the revoked ticket.

"},{"location":"fep/3ab2/#subscription-management","title":"Subscription Management","text":"

All subscription endpoints require both: - A valid authentication credential. - A valid, non-expired ticket cookie associated with the authenticated Principal.

Violating either condition MUST result in 401 Unauthorized.

"},{"location":"fep/3ab2/#list-subscriptions-get-subscriptions_url","title":"List Subscriptions \u2014 GET {subscriptions_url}","text":"

Response:

{\n  \"topics\": [\"notifications\", \"timeline:home\"]\n}\n
Field Type Description topics string\u00a0array The full set of subscribed topics. An empty array indicates no active subscriptions."},{"location":"fep/3ab2/#add-subscription-post-subscriptions_url","title":"Add Subscription \u2014 POST {subscriptions_url}","text":"

Request body:

{\n  \"topics\": [\n    \"remote-server.example/note/abcd\"\n    \"server.example/note/#\",\n  ]\n}\n
Field Type Required Description topics string\u00a0array yes The topic identifiers or patterns

Response:

{\n  \"topics\": [\n    \"remote-server.example/note/abcd\"\n    \"server.example/note/#\",\n    \"server.example/another-topic\"\n  ]\n}\n
Field Type Description topics string\u00a0array The full set of subscribed topics. The array reflects the full set of subscriptions after the operation. Adding a topic that is already subscribed is idempotent and MUST NOT return an error."},{"location":"fep/3ab2/#remove-subscription-delete-subscriptions_urltopictopic","title":"Remove Subscription \u2014 DELETE {subscriptions_url}?topic={topic}","text":"

The topic query parameter is required. Removing a non-existent topic MUST be idempotent (no error).

Response:

"},{"location":"fep/3ab2/#sse-stream-endpoint","title":"SSE Stream Endpoint","text":""},{"location":"fep/3ab2/#opening-the-stream-get-stream_url","title":"Opening the Stream \u2014 GET {stream_url}","text":"

Requires: - A valid authentication credential. - A valid, non-expired ticket cookie whose associated identity matches the Principal.

The client MUST send Accept: text/event-stream.

On success the server: 1. Upgrades the connection to an SSE stream (Content-Type: text/event-stream). 2. Sets Cache-Control: no-cache and (if behind a reverse proxy) X-Accel-Buffering: no. 3. Begins delivering ServerSentEvent frames for all topics the user is currently subscribed to. 4. Keeps the connection open until the client disconnects, the ticket expires, the session is revoked, or the server restarts.

Only one active stream per Principal SHOULD be supported at a time; if a second stream is opened the server MAY close the first.

The ticket is single-use. The server MUST only create one stream per ticket.

"},{"location":"fep/3ab2/#activitypub-sse-event-format","title":"ActivityPub SSE Event Format","text":"

Each event follows the W3C EventSource wire format:

id: <event-id>\\n\nevent: activitypub\\n\ndata: <JSON-object>\\n\n\\n\n
SSE field Value id The id field of the payload object, or a server-generated UUID hex event activitypub (implementations MAY define additional named event types) data A JSON-serialized Activitypub entity

Example ActivityPub Event Payload (data)

{\n  \"@context\":\"https://www.w3.org/ns/activitystreams\",\n  \"id\": \"https://example.com/activities/abc123\",\n  \"type\": \"Create\",\n  \"published\": \"2026-03-10T11:00:00Z\",\n  \"object\": { ... }\n}\n
"},{"location":"fep/3ab2/#heartbeats","title":"Heartbeats","text":"

Servers SHOULD send periodic SSE heartbeat events (event: heartbeat) at regular intervals (RECOMMENDED: every 15\u201330 seconds) to prevent proxy timeouts and help clients detect dead connections. The id and data fields are optional in these messages.

"},{"location":"fep/3ab2/#security-considerations","title":"Security Considerations","text":""},{"location":"fep/3ab2/#ticket-security","title":"Ticket Security","text":""},{"location":"fep/3ab2/#cookie-security","title":"Cookie Security","text":""},{"location":"fep/3ab2/#authorization","title":"Authorization","text":""},{"location":"fep/3ab2/#topic-namespace","title":"Topic Namespace","text":""},{"location":"fep/3ab2/#transport-security","title":"Transport Security","text":""},{"location":"fep/3ab2/#privacy-considerations","title":"Privacy Considerations","text":"

The SSE stream may carry sensitive information (notifications, timeline events). Implementations MUST: - Restrict stream access to the owning Principal. - Limit streaming events to content that the Principal is authorized to view. - Not include events from topics the Principal has not explicitly subscribed to. - Consider data minimization: deliver only the fields necessary for the client's use case.

"},{"location":"fep/3ab2/#implementations","title":"Implementations","text":""},{"location":"fep/3ab2/#alternative-approaches","title":"Alternative Approaches","text":"

Another common technique for implementing event stream is to use Websockets. SSE has several advantages over WebSockets for streaming purposes.

"},{"location":"fep/3ab2/#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/3b86/","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.

"},{"location":"fep/3b86/#1-requirements","title":"1. Requirements","text":"

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.

"},{"location":"fep/3b86/#action-redirecting-to-a-url","title":"Action: Redirecting to a URL","text":"

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)

"},{"location":"fep/3b86/#35-endpoint-expectations","title":"3.5 Endpoint Expectations","text":"

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.

"},{"location":"fep/3b86/#36-publishing-intents-for-application-actors","title":"3.6 Publishing Intents for @application Actors","text":"

For most implementations, Activity Intent endpoints will be identical for every user account on the server. For instance, Mastodon's /share endpoint works for every account, and only varies based on the authentication headers passed to the server.

If a home server uses the same intent locations for every actor, home servers SHOULD include these values in the WebFinger results for the @application actor, in addition to each individual actor.

Doing this improves compatibility across the Fediverse, and enables remote servers to only request a visitor's server name (example.social) instead of their full WebFinger handle (@username@example.social)

"},{"location":"fep/3b86/#4-standard-intent-definitions","title":"4. Standard Intent Definitions","text":""},{"location":"fep/3b86/#41-accept-intent","title":"4.1. Accept Intent","text":"

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":""},{"location":"fep/3b86/#412-example","title":"4.1.2. Example","text":"
{\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":""},{"location":"fep/3b86/#422-example","title":"4.2.2. Example","text":"
{\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":""},{"location":"fep/3b86/#432-example","title":"4.3.2. Example","text":"
{\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":"fep/3b86/#442-example","title":"4.4.2. Example","text":"
{\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":""},{"location":"fep/3b86/#452-example","title":"4.5.2. Example","text":"
{\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 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":"

Discussion and Threading * {audience} - (optional) The ID of the audience to pre-populate into the created object. * {context} - (optional) The ID of the context that this object belongs to. * {inReplyTo} - (optional)The ID of the ActivityStreams Document that this object is inReplyTo.

Other 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.

"},{"location":"fep/3b86/#462-recommendations","title":"4.6.2 Recommendations","text":"

In ActivityPub, the Create activity has many uses, such as creating new posts, replying to existing posts, and even sharing external web content back into the Fediverse. All of these actions can all be done using various tokens present in the Create intent template.

To maximize compatibility, home servers SHOULD include the following tokens in their Create template strings.

Clients (and client libraries) can determine if a home server supports any of these use cases by inspecting the template strings for the corresponding tokens. For instance, a remote server can confirm that the {inReplyTo} token is present in the Create intent template. If it is present, then the remote server can display or activate a \"reply to\" link on its content page.

"},{"location":"fep/3b86/#463-share-example","title":"4.6.3. Share Example","text":"
{\n    \"rel\": \"https://w3id.org/fep/3b86/Create\",\n    \"template\": \"https://mastodon.social/share?text={content}\"\n}\n

Several servers implement \"share\" buttons that let users share web page content back into the Fediverse. To do this, they pre-populate the {content} token of the Activity Intent with the URL of the web page to be shared.

"},{"location":"fep/3b86/#464-reply-example","title":"4.6.4 Reply Example","text":"
{\n    \"rel\": \"https://w3id.org/fep/3b86/Create\",\n    \"template\": \"https://server.social/@me/create?inReplyTo={inReplyTo}\"\n}\n

Remote servers can inspect the template string to determine if replies are supported or not. If the template string includes an {inReplyTo} token, then the home server can accept posts that are direct replies to other ActivityPub objects.

"},{"location":"fep/3b86/#465-threaded-discussion-example","title":"4.6.5 Threaded Discussion Example","text":"
{\n    \"rel\": \"https://w3id.org/fep/3b86/Create\",\n    \"template\": \"https://server.social/@me/create?audience={audience}&context={context}\"\n}\n

Threaded discussions such as forums and bulletin boards typically use {audience} and {context} tokens to place content into the correct part of a discussion. Home servers that want their users to participate in threaded discussions SHOULD publish these tokens in the template string. Endpoints can then pre-populate the values into the resulting activity.

"},{"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":""},{"location":"fep/3b86/#472-example","title":"4.7.2. Example","text":"
{\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":""},{"location":"fep/3b86/#482-example","title":"4.8.2. Example","text":"
{\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":""},{"location":"fep/3b86/#492-example","title":"4.9.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4102-example","title":"4.10.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4112-example","title":"4.11.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4122-example","title":"4.12.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4132-example","title":"4.13.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4142-example","title":"4.14.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4152-example","title":"4.15.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4162-example","title":"4.16.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4172-example","title":"4.17.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4182-example","title":"4.18.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4192-example","title":"4.19.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4202-example","title":"4.20.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4212-example","title":"4.21.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4222-example","title":"4.22.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4232-example","title":"4.23.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4242-example","title":"4.24.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4252-example","title":"4.25.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4262-example","title":"4.26.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4272-example","title":"4.27.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4282-example","title":"4.28.2. Example","text":"
{\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.

"},{"location":"fep/3b86/#511-parameters","title":"5.1.1. Parameters","text":""},{"location":"fep/3b86/#512-example","title":"5.1.2. Example","text":"
{\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.

"},{"location":"fep/3b86/#62-openunvalidated-redirects","title":"6.2 Open/Unvalidated Redirects","text":"

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/#63-malicious-template-strings","title":"6.3 Malicious Template Strings","text":"

When generating redirect URLs from Activity Intent template strings, remote clients MUST apply the following validations to the generated URL before allowing users to use that Activity Intent.

If the generated URL fails any of the tests below, the client MUST erase the result and treat the result as if no template URL was provided for that Activity Intent. This prevents malicious servers from accessing data on the user's local machine or local network.

"},{"location":"fep/3b86/#631-https-only","title":"6.3.1. HTTPS Only","text":"

URLs MUST use HTTPS protocol, i.e. URLs MUST begin with the string https://. All other protocols MUST be rejected.

If a URL fails any of this test, the client MUST treat it as if no template URL was provided by the server. This prevents a malicious server from using Activity Intents to execute code (via a javascript: protocol), access data from the user's local machine (via the file:// protocol), an unsecured device (via the http:// protocol), or another unexpected resource.

"},{"location":"fep/3b86/#632-public-network-only","title":"6.3.2. Public Network Only","text":"

URLs MUST point to a public-facing server location, i.e. URLs MUST NOT allow local network addresses. This includes private IPv4 network addresses, private IPv6 network addresses, loopback addresses, localhost, and *local domains.

If a URL fails this test, the client MUST treat it as if no template URL was provided by the server. This prevents malicious home servers from using Activity Intents to access data on a user's local machine (via localhost and loopback addresses) or using the user as a proxy into their local network (via 192.168.X.X and similar local network addresses).

"},{"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:

  1. A user visits a remote server (i.e., any address on the Internet where they do not have an ActivityPub account)
  2. The remote server displays buttons such as \"like\" or \"share\" for a particular document or object.
  3. When the user clicks on one of these links, the remote server checks to see if the user is already recognized. This does not necessarily mean logged in, or authenticated, just whether or not the user has entered their home server credentials before. True authentication on the remote server is not necessary for Activity Intents to work.
    1. If the user is not recognized, then they are prompted to enter their Fediverse ID (such as @benpate@mastodon.social). The remote server can save this information for use in the future (preferably in a private data store, such as localStorage)
    2. If the user is recognized, this means they have already completed step 3.1, and can continue to step 4.
  4. The remote server uses a standard WebFinger query to look up the user's Fediverse ID and searches for Activity Intents supported by the user's home server.
    1. If none are found, the remote server MAY try to substitute fallback values for known server types.
    2. If none are found (and no fallbacks substituted) the remote server SHOULD inform the user that their account is incompatible with the selected feature and halt the workflow.
  5. The remote server replaces values into the designated URI Template and forwards the user to the assigned page on their home server. This initiates the Activity Intent workflow on their home server.
  6. When the user completes the workflow, the home server SHOULD use URL in the on-success parameter to redirect the user back to the correct page on the remote server.
    1. Similarly, if the user cancels the workflow, the home server SHOULD use the URL in the on-cancel parameter to redirect the user back to the correct page on the remote server.
"},{"location":"fep/3b86/#72-fallbacks-for-unpublished-links","title":"7.2. Fallbacks for Unpublished Links","text":"

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.

"},{"location":"fep/3b86/#80-implementations","title":"8.0. Implementations","text":""},{"location":"fep/3b86/#81-home-servers","title":"8.1 Home Servers","text":"

This is a list of \"home servers\" that publish Activity Intent endpoints for users' profiles.

"},{"location":"fep/3b86/#82-remote-servers","title":"8.2 Remote Servers","text":"

Applications that use Activity Intents to create interactive links and buttons that pull users from remote websites back to their home servers.

"},{"location":"fep/3b86/#83-libraries","title":"8.3 Libraries","text":""},{"location":"fep/3b86/#references","title":"References","text":""},{"location":"fep/3b86/#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/400e/","title":"FEP-400e: Publicly-appendable ActivityPub collections","text":"Authors Status Date received Date final Tracking issue Discussions Repository Gregory Klyushnikov activitypub@grishka.me FINAL 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.

"},{"location":"fep/400e/#specifying-collections-in-actors","title":"Specifying collections in actors","text":"

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":"Using target 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:

"},{"location":"fep/400e/#discussion","title":"Discussion","text":"

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.

"},{"location":"fep/400e/#adding-an-object-to-a-collection","title":"Adding an object to a collection","text":"

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:

"},{"location":"fep/400e/#receiving-an-add-activity","title":"Receiving an Add activity","text":"

When an ActivityPub server receives in its inbox a correctly signed Add activity, it SHOULD do the following:

"},{"location":"fep/400e/#example-of-an-add-activity","title":"Example of an 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.

"},{"location":"fep/400e/#moving-an-object-between-collections","title":"Moving an object between collections","text":"

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.

"},{"location":"fep/400e/#security-considerations","title":"Security considerations","text":"

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.

"},{"location":"fep/400e/#implementations","title":"Implementations","text":"

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":""},{"location":"fep/400e/#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/4adb/","title":"FEP-4adb: Dereferencing identifiers with webfinger","text":"Authors Status Date received Tracking issue Discussions Repository Helge helge.krueger@gmail.com DRAFT 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:

  1. Fetch GET https://domain/.well-known/webfinger?resource={URI}.
  2. Determine the link property with \"type\":\"application/activity+json\".
  3. Dereference this object.

We will denote this object by ActivityStreams(URI, domain). There are now two cases to resolve an URI:

  1. The URI determines the domain denoted by domain(URI)
  2. The URI doesn't determine the domain

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.

"},{"location":"fep/4adb/#examples","title":"Examples","text":"

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.

"},{"location":"fep/4adb/#applications-to-the-actor-object","title":"Applications to the Actor Object","text":"

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.

"},{"location":"fep/4adb/#dns-names","title":"DNS Names","text":"

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.

"},{"location":"fep/4adb/#using-didkey","title":"Using did:key","text":"

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":""},{"location":"fep/4adb/#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/4ccd/","title":"FEP-4ccd: Pending Followers Collection and Pending Following Collection","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@prodromou.name DRAFT 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.

"},{"location":"fep/4ccd/#motivation","title":"Motivation","text":"

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.

All terms in the context document use the same namespace, https://purl.archive.org/socialweb/pending#.

"},{"location":"fep/4ccd/#properties","title":"Properties","text":"

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.

"},{"location":"fep/4ccd/#actor-with-pendingfollowers-and-pendingfollowing","title":"Actor with 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.

"},{"location":"fep/4ccd/#pendingfollowers-collection","title":"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.

"},{"location":"fep/4ccd/#pendingfollowing-collection","title":"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.

"},{"location":"fep/4ccd/#processing-requirements","title":"Processing requirements","text":"

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.

"},{"location":"fep/4ccd/#security-considerations","title":"Security considerations","text":"

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.

"},{"location":"fep/4ccd/#implementations","title":"Implementations","text":""},{"location":"fep/4ccd/#references","title":"References","text":""},{"location":"fep/4ccd/#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/4f05/","title":"FEP-4f05: Soft Deletion","text":"Authors Status Date received Tracking issue Discussions Repository Julian Lam julian@nodebb.org, Angus McLeod angus@pavilion.tech DRAFT 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:

The Forums and Threaded Discussions Task Force (ForumWG) has identified a common nomenclature when referring to organized objects in a threaded discussion model.

"},{"location":"fep/4f05/#publishers","title":"Publishers","text":""},{"location":"fep/4f05/#soft-deletion","title":"Soft deletion","text":"

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.

"},{"location":"fep/4f05/#hard-deletion","title":"Hard deletion","text":"

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.

"},{"location":"fep/4f05/#receivers","title":"Receivers","text":"

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.

"},{"location":"fep/4f05/#tombstone","title":"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 a Tombstone","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.

"},{"location":"fep/4f05/#additional-considerations","title":"Additional Considerations","text":""},{"location":"fep/4f05/#update-activity","title":"Update activity","text":"

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.

"},{"location":"fep/4f05/#broad-support-or-lack-thereof","title":"Broad support (or lack thereof)","text":"

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.

"},{"location":"fep/4f05/#implementor-ux","title":"Implementor UX","text":"

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.

"},{"location":"fep/4f05/#implementors","title":"Implementors","text":""},{"location":"fep/4f05/#references","title":"References","text":""},{"location":"fep/4f05/#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/5219/","title":"FEP-5219: Groups and permissions","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT implementation 2026-06-11 #860 Discussions codeberg"},{"location":"fep/5219/#summary","title":"Summary","text":"

This document describes a permission system for ActivityPub groups.

It is inspired by XEP-0045: Multi-User Chat, which defines a comprehensive permission system for XMPP group chats.

"},{"location":"fep/5219/#motivation","title":"Motivation","text":"

The most widely used ActivityPub group implementation is described in FEP-1b12 (Group federation). That document was focused on public groups, and private FEP-1b12 groups were later proposed in Lemmy RFC-0005 (Private Communities). However, the mechanism for controlling user permissions in such groups has not been clearly defined.

This FEP formalizes the mechanisms that are already used in FEP-1b12 groups, and extends them to accommodate new group types, such as semi-private groups and group chats. The proposed permission system can also be used in other contexts, such as when designating server moderators or establishing organizational hierarchies.

"},{"location":"fep/5219/#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/5219/#affiliation","title":"Affiliation","text":"

Affiliation is a long-lived association with a group.

The default set of affiliations includes:

This list is not exhaustive, and implementations MAY support additional affiliations if necessary. It is also possible to have no affiliation with a group.

The group actor itself is considered to have an admin affiliation.

Affiliations can be specified using the affiliations collection attached to a Group actor. It is an ordered collection where each item is a Relationship object that represents a relationship between a user and a group. This object has the following fields:

The affiliations collection is sorted from most privilege to least privilege.

When adding a Relationship for a custom affiliation, a fallback representation SHOULD also be included. This fallback representation MUST specify an affiliation from the default set that has equal or fewer privileges compared to the custom affiliation.

Consumers MUST ignore relationships that represent unsupported affiliations.

The affiliations collection supersedes the collection of moderators defined in FEP-1b12. Consumers MUST read the affiliations collection when both collections are present.

Example of an affiliations collection:

{\n  \"id\": \"https://social.example/group/affiliations\",\n  \"type\": \"OrderedCollection\",\n  \"attributedTo\": \"https://social.example/group\",\n  \"items\": [\n    {\n      \"type\": \"Relationship\",\n      \"subject\": \"https://social.example/alice\",\n      \"relationship\": \"admin\"\n    },\n    {\n      \"type\": \"Relationship\",\n      \"subject\": \"https://social.example/bob\",\n      \"relationship\": \"member\"\n    }\n  ]\n}\n

The collection is modified using Add and Remove activities. They have the following properties:

Example:

{\n  \"id\": \"https://social.example/activities/1\",\n  \"type\": \"Add\",\n  \"object\": {\n    \"type\": \"Relationship\",\n    \"subject\": \"https://social.example/bob\",\n    \"relationship\": \"member\"\n  },\n  \"target\": \"https://social.example/group/affiliations\",\n  \"to\": [\n    \"https://social.example/group/followers\"\n  ]\n}\n
"},{"location":"fep/5219/#participation","title":"Participation","text":"

Participation refers to the ability to read or write to a group.

There are two levels of participation:

The participation status is distinct from affiliation. Affiliations MAY be preserved when users leave and re-join the group.

A group where anyone can participate is a public group.

A group where only members can participate is a private group.

A group which anyone can follow but where only members can participate is a semi-private group.

Group actors MUST have a followers property pointing to the collection of group followers.

Semi-private group actors MUST have a participants property pointing to the collection of group participants. In public and private groups, the collection of participants is identical to the collection of followers.

"},{"location":"fep/5219/#privileges","title":"Privileges","text":"Privilege Activity Admin Moderator Member None Outcast Follow a public group Follow Yes Yes Yes Yes No Participate in a public group - Yes Yes Yes Yes No Follow a semi-private group Follow Yes Yes Yes Yes No Participate in a semi-private group Join Yes Yes Yes No No Follow a private group Join or Follow Yes Yes Yes No No Participate in a private group Join or Follow Yes Yes Yes No No Remove posts of other participants Delete Yes Yes No No No Remove participants Reject Yes Yes No No No Ban participants Block Yes Yes No No No Add or remove members Add or Remove Yes Yes No No No Add or remove moderators Add or Remove Yes No No No No Update group metadata Update Yes No No No No

This list of privileges is not exhaustive. Additional privileges MAY be associated with the affiliations from the default set.

"},{"location":"fep/5219/#activities","title":"Activities","text":"

Group activities (such as FEP-1b12 Announce activities) MUST be addressed to the group's followers collection or to its participants collection.

Recipients of a group activity SHOULD resolve the group's affiliations collection.

All participants are allowed to update and delete their own posts, and to remove themselves from the group. Actions that affect other participants MAY be authorized if the actor has the necessary privileges.

"},{"location":"fep/5219/#references","title":"References","text":""},{"location":"fep/5219/#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/521a/","title":"FEP-521a: Representing actor's public keys","text":"Authors Status Type Date received Date final Tracking issue Discussions Repository silverpill @silverpill@mitra.social FINAL 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.

"},{"location":"fep/521a/#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/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:

"},{"location":"fep/521a/#key-ids","title":"Key IDs","text":"

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.

"},{"location":"fep/521a/#test-vectors","title":"Test vectors","text":"

See fep-521a.feature

"},{"location":"fep/521a/#implementations","title":"Implementations","text":""},{"location":"fep/521a/#references","title":"References","text":""},{"location":"fep/521a/#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/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.

"},{"location":"fep/521a/fep-521a-test-case/#about-this-test","title":"About this Test","text":"

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:

"},{"location":"fep/521a/fep-521a-test-case/#applicability","title":"Applicability","text":"

This test applies directly to the actor input.

"},{"location":"fep/521a/fep-521a-test-case/#test-targets","title":"Test Targets","text":""},{"location":"fep/521a/fep-521a-test-case/#expectations","title":"Expectations","text":"
  1. assertionMethod[x].id - MUST be a string
  2. assertionMethod[x].controller - MUST match the id property, if present, of the assertionMethod array's parent (i.e., the Actor object)
  3. assertionMethod[x].publicKeyMultibase - MUST be a base58btc-encoded and appropriately-prefixed (in this case, beginning with z) expression of a binary public key expression
"},{"location":"fep/521a/fep-521a-test-case/#assumptions","title":"Assumptions","text":""},{"location":"fep/521a/fep-521a-test-case/#1-how-to-determine-whether-actor-object-is-expressing-a-key-in-the-fep-defined-shape","title":"1. How to Determine Whether Actor Object is expressing a key in the FEP-defined shape","text":"

For the purposes of determining whether the input actor is expressing a key in the shape defined by the FEP:

"},{"location":"fep/521a/fep-521a-test-case/#2-property-value-expectations","title":"2. Property value expectations","text":"

For the purposes of determining whether the input actor is expressing 1 or more keys all the following must be true:

"},{"location":"fep/521a/fep-521a-test-case/#test-cases","title":"Test Cases","text":"

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

"},{"location":"fep/521a/fep-521a-test-case/#misshapen-assertionmethod-array","title":"Misshapen assertionMethod Array","text":"

input

actor:

{\n    \"type\": \"Person\",\n    \"inbox\": \"https://example.com/inbox\",\n    \"outbox\": \"https://example.com/outbox\",\n    \"assertionMethod\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n}\n

test targets

"},{"location":"fep/521a/fep-521a-test-case/#misshapen-assertionmethod-members","title":"Misshapen assertionMethod Members","text":"

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

"},{"location":"fep/521a/fep-521a-test-case/#malformed-publickeymultibase-value","title":"Malformed publicKeyMultibase Value","text":"

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

"},{"location":"fep/521a/fep-521a-test-case/#valid-actor","title":"Valid Actor","text":"

input

test targets

"},{"location":"fep/521a/fep-521a-test-case/#glossary","title":"Glossary","text":""},{"location":"fep/521a/fep-521a-test-case/#outcome","title":"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:

"},{"location":"fep/521a/fep-521a-test-case/#requirements-mapping","title":"Requirements Mapping","text":""},{"location":"fep/521a/fep-521a-test-case/#change-log","title":"Change Log","text":""},{"location":"fep/521a/fep-521a-test-case/#issues-list","title":"Issues List","text":""},{"location":"fep/5624/","title":"FEP-5624: Per-object reply control policies","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Claire claire.fep-1d7d@sitedethib.com WITHDRAWN 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.

"},{"location":"fep/5624/#receiving-and-accepting-a-reply","title":"Receiving and accepting a reply","text":"

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.

"},{"location":"fep/5624/#example-approvereply-activity","title":"Example 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.

"},{"location":"fep/5624/#example-reply-object-with-replyapproval","title":"Example reply object with 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.

"},{"location":"fep/5624/#revoking-a-previously-accepted-reply","title":"Revoking a previously-accepted reply","text":"

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.

"},{"location":"fep/5624/#handling-a-revocation","title":"Handling a revocation","text":"

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

"},{"location":"fep/5624/#deployment-considerations","title":"Deployment considerations","text":"

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:

"},{"location":"fep/5624/#implementations","title":"Implementations","text":"

None so far.

"},{"location":"fep/5624/#references","title":"References","text":""},{"location":"fep/5624/#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/5711/","title":"FEP-5711: Inverse Properties for Collections","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.org DRAFT 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":""},{"location":"fep/5711/#specification","title":"Specification","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":"

Defines 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":"

Defines 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":"

Defines 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":"

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":"

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":"

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":"

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":"

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.

"},{"location":"fep/5711/#references","title":"References","text":""},{"location":"fep/5711/#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/5bf0/","title":"FEP-5bf0: Collection sorting and filtering","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Michael Puckett michael@puckett.contact WITHDRAWN 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.

"},{"location":"fep/5bf0/#references","title":"References","text":""},{"location":"fep/5bf0/#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/5e53/","title":"FEP-5e53: Opt-out Preference Signals","text":"Authors Status Date received Tracking issue Discussions Repository Don Marti dmarti@zgp.org DRAFT 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.

"},{"location":"fep/5e53/#deployment-considerations","title":"Deployment considerations","text":"

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":""},{"location":"fep/5e53/#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/5feb/","title":"FEP-5feb: Search indexing consent for actors","text":"Authors Status Date received Tracking issue Discussions Repository Claire claire.fep-1d7d@sitedethib.com DRAFT 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.

"},{"location":"fep/5feb/#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/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.

"},{"location":"fep/5feb/#example","title":"Example","text":"
{\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).

"},{"location":"fep/5feb/#handling-updates-to-the-indexable-attribute","title":"Handling updates to the 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.

"},{"location":"fep/5feb/#security-considerations","title":"Security considerations","text":"

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":""},{"location":"fep/5feb/#references","title":"References","text":""},{"location":"fep/5feb/#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/612d/","title":"FEP-612d: Identifying ActivityPub Objects through DNS","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Helge helge.krueger@gmail.com WITHDRAWN 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.

"},{"location":"fep/612d/#discussion","title":"Discussion","text":"

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.

"},{"location":"fep/612d/#html-link-header","title":"HTML Link header","text":"

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":""},{"location":"fep/612d/#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/61cf/","title":"FEP-61cf: The OpenWebAuth Protocol","text":"Authors Status Type Date received Tracking issue Discussions Repository FenTiger @FenTiger@zotum.net DRAFT 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:

"},{"location":"fep/61cf/#starting-the-login-flow","title":"Starting the login flow","text":"

The OpenWebAuth login flow can begin in one of two ways:

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.

"},{"location":"fep/61cf/#operation-of-the-protocol","title":"Operation of the protocol","text":"

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.

"},{"location":"fep/61cf/#3-target-instance-provides-a-token","title":"3. Target instance provides a token","text":"

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.

"},{"location":"fep/61cf/#4-home-instance-receives-the-token","title":"4. Home instance receives the token","text":"

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.

"},{"location":"fep/61cf/#5-user-returns-to-target-instance","title":"5. User returns to target instance","text":"

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.

"},{"location":"fep/61cf/#target-instances-login-check","title":"Target instance's login check","text":"

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.

"},{"location":"fep/61cf/#target-instances-token-endpoint","title":"Target instance's token endpoint","text":"

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":""},{"location":"fep/61cf/#security-considerations","title":"Security Considerations","text":""},{"location":"fep/61cf/#information-leakage","title":"Information leakage","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.

"},{"location":"fep/61cf/#impersonation-attack","title":"Impersonation attack","text":"

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.

"},{"location":"fep/61cf/#open-redirection","title":"Open redirection","text":"

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.

"},{"location":"fep/61cf/#references","title":"References","text":""},{"location":"fep/61cf/#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/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.uk WITHDRAWN 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.

"},{"location":"fep/6481/#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/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":""},{"location":"fep/6481/#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/6606/","title":"FEP-6606: ActivityPub client to server collections addressing conventions","text":"Authors Status Date received Tracking issue Discussions Repository Marius Orcsik marius@federated.id DRAFT 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":""},{"location":"fep/6606/#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/67ff/","title":"FEP-67ff: FEDERATION.md","text":"Authors Status Date received Date final Tracking issue Discussions Repository silverpill @silverpill@mitra.social FINAL 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.

"},{"location":"fep/67ff/#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/67ff/#structure","title":"Structure","text":"

The FEDERATION.md file can have arbitrary structure and content. The only requirements are:

"},{"location":"fep/67ff/#template","title":"Template","text":"

(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":""},{"location":"fep/67ff/#references","title":"References","text":""},{"location":"fep/67ff/#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/6fcd/","title":"FEP-6fcd: Account Export Container Format","text":"Authors Status Date received Tracking issue Discussions Repository Dmitri Zagidulin @dmitri@social.coop DRAFT 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:

"},{"location":"fep/6fcd/#inspirations-and-prior-art","title":"Inspirations and Prior Art","text":""},{"location":"fep/6fcd/#overall-concept","title":"Overall Concept","text":"
  1. Serialize export data into files and directories
  2. Add a lightweight manifest.yml file that describes what's in the files and directories
  3. Wrap everything into a .tar file.
"},{"location":"fep/6fcd/#manifest-file","title":"Manifest File","text":""},{"location":"fep/6fcd/#reserved-properties","title":"Reserved Properties","text":""},{"location":"fep/6fcd/#activitypub-export-example","title":"ActivityPub Export Example","text":"

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":""},{"location":"fep/6fcd/#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/73cd/","title":"FEP-73cd: Migration User Stories","text":"Authors Status Date received Tracking issue Discussions Repository Bumblefudge <@learningproof.xyz> / @by_caballero@mastodon.social DRAFT 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.

  1. Alice wants to move her account from Alpha to Gamma, both of which are online and federated to one another, without losing her follow relationships in either direction. Four possible variants, not mutually exclusive:
  2. Bob is asked to leave Alpha by its moderation team, who have disabled new posts on that account but are allowing Bob to execute a one-way, permanent migration to a new server of Bob's choosing as a courtesy. Bob logs in to Alpha and permanently migrates to a new server with which Alpha is federated, allowing a server-to-server connection. Bob instead finds a new home on the server Beta, which is specifically de-federated by Alpha for incompatible moderation policies. Bob would like to announce to his followers his new account, without Alpha and Beta having to communicate with one another (since Alpha refuses connections with Beta altogether).
  3. Charlie would like to move his profile, followers, and his years of original content to Gamma from Delta, because the latter was recently and unexpectedly taken offline by government intervention. Before going dark, Gamma had already authorized a custom client for Charlie, which he used to sign each posts with a self-managed private key, and Charlie had backed up his followers/following lists. Delta is able to load all of the above and authenticate them to display on his new account.
  4. Bob would also like to move his previous content from Alpha to Beta, which are defederated. After having his service partially limited, he is able to export his archive of Alpha content (including media uploads but no moderation records, as Alpha does not generate any) and import it to Beta to be considered for republication.
  5. Charlie also managed to expert an archive of his Gamma content after authorizing Charlie's custom client (including media uploads and moderation records), and is able to use said client to authenticate this content to Delta, who can consider it for republication despite Gamma being offline.
  6. After using Delta for some time, Charlie moves on to server Epsilon, which Daniel wants to migrate to from Delta. Epsilon has no moderation policy because Daniel is its only user and he has full admin rights over it. Epsilon loads a recent backup, skipping over the moderation policies exported by Delta.
  7. At time of import, Delta does not support all the same features and Activity types that Gamma did at time of export. Two variants, not mutually exclusive:
"},{"location":"fep/73cd/#use-case-to-fep-mapping","title":"Use-Case to FEP Mapping","text":"

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>)...

Use cases: 1A 1B 1C 1D 2 3 4 5 6 7A 7B FEP-7628 \"Move Actor\" (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":""},{"location":"fep/73cd/#references","title":"References","text":""},{"location":"fep/73cd/#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/7458/","title":"FEP-7458: Using the replies collection","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 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.

"},{"location":"fep/7458/#problem-statement","title":"Problem statement","text":"

(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.

"},{"location":"fep/7458/#example-flow","title":"Example flow","text":"

(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.

"},{"location":"fep/7458/#behavioral-requirements","title":"Behavioral requirements","text":""},{"location":"fep/7458/#references","title":"References","text":""},{"location":"fep/7458/#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/7502/","title":"FEP-7502: Limiting visibility to authenticated actors","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 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.

"},{"location":"fep/7502/#prior-art","title":"Prior art","text":"

(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.

"},{"location":"fep/7502/#proposal","title":"Proposal","text":"

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":""},{"location":"fep/7502/#implementations","title":"Implementations","text":"

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.

"},{"location":"fep/7502/#terms-defined","title":"Terms defined","text":"Authenticated URI 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":""},{"location":"fep/7502/#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/7628/","title":"FEP-7628: Move actor","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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.

"},{"location":"fep/7628/#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/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:

"},{"location":"fep/7628/#migration","title":"Migration","text":"

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:

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:

  1. Move sent by the old actor (push mode). In this case, the old server needs to be online.
  2. 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.

movedTo

The location of the new primary actor. The actor containing this property should be considered inactive.

copiedTo

The location of the new primary actor.

"},{"location":"fep/7628/#references","title":"References","text":""},{"location":"fep/7628/#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/76ea/","title":"FEP-76ea: Conversation Threads","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.org DRAFT 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.

  1. As an ActivityPub developer, I want to be able to determine if two objects are part of the same conversation thread, so that I can display them together in a conversation view. This is possible to do by walking up the 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.
  2. As an ActivityPub developer, I want to get the full conversation thread of an object efficiently, so I can show the object in context. Again, using the inReplyTo chain and replies collections can be slow and inefficient, especially if the thread is deep or has many objects.
  3. As an ActivityPub developer, I want to get the most-recently-added objects in a conversation thread, so I can keep my view of the thread up-to-date. This is hard to do with inReplyTo and replies, and possibly requires fetching every single replies collection in the tree.
  4. As an ActivityPub developer, I want to be able to identify the original post of a conversation thread, so I can show the thread in context. This content object is usually treated differently by clients, so it's useful to be able to identify it.
  5. As a commenter, I want my reply to a content object to be part of the same thread, so I am participating in a larger context.
  6. As the original poster, I want to be able to curate the conversation thread, so I can remove spam, off-topic, or abusive content from the thread. Maintaining a curated thread is an important service that the original poster provides. Curating the replies collection is not sufficient, because it only contains direct replies to the object, not the full conversation tree.
  7. As a user, I want to comment on or about a content object without being part of the conversation thread, so I can discuss it in my own social sphere. Not all continued conversation should be part of the original reply tree.
  8. As a user, I want to branch a reply into its own conversation thread, so I can have a separate conversation about a sub-topic. This is common in forums and other threaded discussion software.
  9. As a user, I want to graft part of a conversation thread into another thread, so I can consolidate related conversations. This is common in forums and other threaded discussion software.
"},{"location":"fep/76ea/#context","title":"Context","text":"

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.

"},{"location":"fep/76ea/#root","title":"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.

"},{"location":"fep/76ea/#behavioral-specification","title":"Behavioral specification","text":"

This covers recommended behavior for processors that implement the thread property.

"},{"location":"fep/76ea/#original-posts","title":"Original posts","text":"

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.

"},{"location":"fep/76ea/#replies-in-the-thread","title":"Replies in the thread","text":"

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.

"},{"location":"fep/76ea/#branching","title":"Branching","text":"

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.

"},{"location":"fep/76ea/#grafting","title":"Grafting","text":"

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.

"},{"location":"fep/76ea/#thread-maintenance","title":"Thread maintenance","text":"

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.

"},{"location":"fep/76ea/#examples","title":"Examples","text":""},{"location":"fep/76ea/#example-1","title":"Example 1","text":"

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.

"},{"location":"fep/76ea/#example-3","title":"Example 3","text":"

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.

"},{"location":"fep/76ea/#previous-work","title":"Previous work","text":"

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.

"},{"location":"fep/76ea/#references","title":"References","text":""},{"location":"fep/76ea/#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/7888/","title":"FEP-7888: Demystifying the context property","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 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.

"},{"location":"fep/7888/#overview","title":"Overview","text":"

(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:

The requirements below can be summarized like so:

"},{"location":"fep/7888/#publishing-context","title":"Publishing context","text":"

When generating an object with a context as a publisher:

"},{"location":"fep/7888/#purpose","title":"Purpose","text":"

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.

"},{"location":"fep/7888/#dereferencing-and-resolving","title":"Dereferencing and resolving","text":"

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:

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.)

"},{"location":"fep/7888/#consuming-context","title":"Consuming context","text":"

When encountering an object with a context as a consumer or browser:

"},{"location":"fep/7888/#group-objects-by-context","title":"Group objects by 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).

"},{"location":"fep/7888/#canonical-collections-of-objects-within-an-authoritative-context","title":"Canonical collections of objects within an authoritative context","text":"

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:

"},{"location":"fep/7888/#interacting-with-context","title":"Interacting with 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:

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.

"},{"location":"fep/7888/#keeping-relevant-entities-in-the-loop","title":"Keeping relevant entities in the loop","text":"

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.

"},{"location":"fep/7888/#appendix-a-rationale","title":"Appendix A: Rationale","text":"

(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:

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:

"},{"location":"fep/7888/#appendix-b-examples","title":"Appendix B: Examples","text":"

(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.

"},{"location":"fep/7888/#example-3-encountering-multiple-contexts","title":"Example 3: Encountering multiple contexts","text":"

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.

"},{"location":"fep/7888/#appendix-c-creating-and-maintaining-contexts-and-their-associated-collections-using-activitypub-c2s","title":"Appendix C: Creating and maintaining contexts and their associated collections using ActivityPub C2S","text":"

(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:

  1. Create the canonical Collection that will be associated with the context. Save the generated Collection id to be used in the next step.
  2. Create the Object that will be used as 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.
  3. Create the Object that will exist within the context, and specify the 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.
  4. Add the Object to the context's canonical Collection, using the 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.
"},{"location":"fep/7888/#references","title":"References","text":""},{"location":"fep/7888/#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/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.xyz DRAFT 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:

  1. Identity hosting (hosting of the Actor profile object, which serves as the source of truth for all others in the list)
  2. Authentication and authorship/provenance of objects
  3. Key management (used for HTTP Signatures for Server to Server request authentication)
  4. ActivityPub Object and Collection storage (typically in a database)
  5. Message Transfer Services (hosting of inboxes and outboxes, delivering of outgoing objects, notifications, etc)
  6. Instance-based content moderation
  7. Community discovery (via the instance's Local feed)
  8. Front-end clients, i.e. \u201cwebsite\u201d and/or mobile app (most current implementations bundle the backend system with a made-for-purpose front end that runs off the same server)

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":"
  1. Sign everything: Recognize client-side cryptographic signatures as proof of authorship, in addition to the current practice of relying solely on the instance URL:
  2. add verification key(s) to the Actor object via FEP-521a
  3. Sign individual activities using the same key(s), as specified in FEP-8b32: Object Integrity Proofs
  4. B.Y.O. Actor ID: Using Object Integrity proofs enables Identity Hosting to be separated from the other instance concerns. Actor profiles can now be hosted separately from the instance, which in turn enables service providers to offer their users a \u201cBYO (Bring Your Own) domain name\u201d feature.
  5. Separate Inbox/Outbox: (Optional) The previous steps enable message transfer and Inbox/Outbox hosting to be outsourced to separate service providers (the Actor profile links to these in the usual manner).
  6. Separate Object and Collection hosting: (Optional) Similarly, AP Objects and Collections can now be stored on domains separate from the Actor's domain (since authorship and controller-ship can be proven cryptographically, in a domain-independent way). This enables the user to migrate storage service providers without having to change their Actor ID.
"},{"location":"fep/7952/#2-implement-portable-migration-stable-object-ids","title":"2. Implement Portable (migration-stable) Object IDs","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:

As 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:

"},{"location":"fep/7952/#3-create-account-exportimport-features-that-assume-actor-relative-urls-and-segmented-services","title":"3. Create account export/import features that assume Actor-Relative URLs and segmented services","text":"

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:

"},{"location":"fep/7952/#references","title":"References","text":""},{"location":"fep/7952/#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/7aa9/","title":"FEP-7aa9: Featuring recommendations using a dedicated collection","text":"Authors Status Date received Tracking issue Discussions Repository David Roetzel david@joinmastodon.org DRAFT 2026-06-12 #862 Discussions codeberg"},{"location":"fep/7aa9/#summary","title":"Summary","text":"

This FEP describes both a new object type and mechanism to allow users to curate collections of other users (actors) and possibly other objects that they would like to recommend to others. These collections, sometimes referred to as \"Starter Packs\", help new users find interesting people and content to follow.

Users are able to opt in to being included in these collections and can remove themselves from them. Problematic collections can be reported and moderated just like other content.

"},{"location":"fep/7aa9/#background","title":"Background","text":"

\"Starter Packs\", a feature pioneered by Bluesky, have proven to be a brilliant way to help new users to find exactly the right people to follow. This can be an important tool to combat the \"empty feed\" problem that many new users experience on the fediverse and that sometimes turns them away. As such, there has been a lot of interest in implementing Starter Packs on the fediverse.

But while the basic idea of a \"Starter Pack\" is quite simple, the federated nature of the network poses some challenges that need to be overcome. For example it should be possible to interact with remote \"Starter Packs\" that were created on a different server and possibly by a different software.

Last but not least, \"Starter Packs\" need to be handled with care as they can be an easy vector for harassment. Users need control over which \"Starter Packs\" they are included in and \"Starter Packs\" need to be subject to the same moderation procedures already in place for other types of content.

GoToSocial has pioneered \"Interaction Policies\" to model a user's preferences for different kinds of interactions. And in FEP-044f Mastodon has expanded on this idea with the addition of verifiable \"stamps\" to prove user's consent.

This FEP takes those concepts and applies them to a user-curated and federated collection of actors (or any kind of object really) called FeaturedCollection. The name was chosen because some platforms already announced they do not plan to use the term \"Starter Pack\" and to illustrate that other uses, i.e. featuring other objects than just actors, are possible.

"},{"location":"fep/7aa9/#representation-of-featured-collections","title":"Representation of Featured Collections","text":"

Featured collections are represented by a new object type, FeaturedCollection (https://w3id.org/fep/7aa9#FeaturedCollection). FeaturedCollection is a subtype of OrderedCollection and inherits all of its properties.

A FeaturedCollection MUST have the following properties:

In addition, a FeaturedCollection MAY have the following properties:

It is worth emphasizing that both icon and image are separately optional. Providers of FeaturedCollections may choose to supply both, only one, or neither. Applications displaying FeaturedCollections may also elect to show or omit either or both images, depending on what makes sense in their UI design and the specific situation. This FEP considers these images decorative in nature, meaning they should not be the only source of important information.

This FEP also introduces a new property that MAY optionally be used in a FeaturedCollection:

The individual items in the FeaturedCollection are of the type FeaturedItem (https://w3id.org/fep/7aa9#FeaturedItem) which is a subtype of Object. A FeaturedItem MUST have the following property:

In the case that the featured object is an actor it MUST also include the following property:

In addition, a FeaturedItem MAY have the following property:

Please note that initially the featured objects are expected to be actors. But the specification is intentionally open to also include other object types. The most obvious one that platforms might want to add in the future is Hashtag.

Example featured collection:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/7aa9\",\n    {\n      \"Hashtag\": \"as:Hashtag\",\n      \"sensitive\": \"as:sensitive\",\n      \"discoverable\": \"https://joinmastodon.org/ns#discoverable\"\n    }\n  ],\n  \"type\": \"FeaturedCollection\",\n  \"id\": \"https://fedi.example.com/users/alice/featured/23\",\n  \"name\": \"Cute cats\",\n  \"summary\": \"A selection of accounts that I follow because of their interesting cat content.\",\n  \"attributedTo\": \"https://fedi.example.com/users/alice\",\n  \"sensitive\": false,\n  \"discoverable\": true,\n  \"topic\": {\n    \"type\": \"Hashtag\",\n    \"name\": \"#cats\"\n  },\n  \"icon\": {\n    \"type\": \"Image\",\n    \"url\": {\n      \"type\": \"Link\",\n      \"mediaType\": \"image/jpeg\",\n      \"href\": \"https://fedi.example.com/assets/alice_sp_23.jpg\"\n    }\n  },\n  \"totalItems\": 2,\n  \"orderedItems\": [\n    {\n      \"id\": \"https://fedi.example.com/users/alice/featured/23/items/1\",\n      \"type\": \"FeaturedItem\",\n      \"featuredObject\": \"https://fedi.example.com/users/jennifer\",\n      \"featureAuthorization\": \"https://fedi.example.com/users/jennifer/stamps/12\",\n      \"published\": \"2025-08-14T12:13:22Z\"\n    },\n    {\n      \"id\": \"https://fedi.example.com/users/alice/featured/23/items/2\",\n      \"type\": \"FeaturedItem\",\n      \"featuredObject\": \"https://other.example.com/users/jim\",\n      \"featureAuthorization\": \"https://other.example.com/users/jim/stamps/21\",\n      \"published\": \"2025-08-14T12:14:51Z\"\n    }\n  ],\n  \"published\": \"2025-08-14T12:12:12Z\",\n  \"updated\": \"2025-08-14T13:17:25\"\n}\n

Very large lists of items do not make much sense from a UX perspective. E.g. not many users will want to blindly follow a couple of hundred of unknown accounts. And forcing remote servers to potentially fetch a lot of unknown actors is a vector for Denial of Service (DOS). That is why fediverse software SHOULD both impose a limit to the number of items that can be added to a featured collection and that they will handle when dealing with remote collections. The proposed maximum of items is 150 but implementations MAY have different limits.

All properties mentioned are expected to have at most one value unless stated otherwise.

"},{"location":"fep/7aa9/#featured-collections-on-actors","title":"Featured Collections on Actors","text":"

Featured collections are created by individual actors. As such they SHOULD become part of a new collection property on the actor, featuredCollections. This way other servers on the fediverse can easily discover them.

"},{"location":"fep/7aa9/#federating-changes-and-opportunistic-updating","title":"Federating Changes and Opportunistic Updating","text":"

When a user creates a new FeaturedCollection, this is then added to their actor's featuredCollections collection, an operation that can be federated as an Add activity.

Similarly, when a FeaturedItem is added to a FeaturedCollection, this can also be distributed in the form of an Add activity.

Remove activities can be sent in case of removal from one of the mentioned collections.

A FeaturedCollection can be discovered by different means, which means a server can never know exactly who knows about it. When addressing the aforementioned activities a server SHOULD thus use a heuristic to determine who to send them to. A reasonable heuristic could be to send all changes to an actor's featuredCollections collection to the actor's followers. Additionally all changes to an individual FeaturedCollection could also go to all actors in that collection (including the one that was just removed in case of Remove).

No such heuristic will ever be perfect though. Servers who know about a FeaturedCollection might still not receive relevant updates. This means that implementations SHOULD try to re-fetch these collections from time to time to make sure the content is still current.

"},{"location":"fep/7aa9/#interaction-policies-for-curated-collections","title":"Interaction Policies for Curated Collections","text":"

Users MUST be able to consent to being included in featured collections. To signal a user's preferences in that regard an interactionPolicy object, as first introduced by GoToSocial, MUST be added to the user's actor. This interactionPolicy MUST have a property canFeature.

Abbreviated example actor:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://gotosocial.org/ns\",\n    \"https://w3id.org/fep/7aa9\"\n  ],\n  \"id\": \"https://example.com/users/alice\",\n  \"type\": \"Person\",\n  \"interactionPolicy\": {\n    \"canFeature\": {\n      \"automaticApproval\": [ \"https://fedi.example.com/users/alice/followers\" ],\n      \"manualApproval\": [ \"https://www.w3.org/ns/activitystreams#Public\" ]\n    }\n  }\n  // ...\n}\n

The two properties of the canFeature object can be used to signal who is always allowed to feature this actor in a featured collection (automaticApproval) and who might do so but would need a manual approval (manualApproval).

The value in both cases MUST be an array consisting of actor objects, Collection of actor objects or the special collection https://www.w3.org/ns/activitystreams#Public. Note that the latter might also be represented as as:Public or simply Public. Implementations SHOULD handle all three possible representations.

In practice, the only values that SHOULD be used are the id of the actor itself (see below), the followers and the following collection of the actor and https://www.w3.org/ns/activitystreams#Public.

Actors not specifically mentioned or included in one of the collections are never allowed to feature the actor.

The absence of an interactionPolicy MUST be treated as missing consent and the affected actors MUST NOT be added to featured collections ever.

To make a policy of never wanting to be featured explicit, interactionPolicy.canFeature.automaticApproval SHOULD contain the actor's id as its single value. This is because an empty array is equivalent to a missing property under JSON-LD canonicalization.

In any case this general policy is just that, a general policy, and MUST NOT be confused with actual consent. This means that one can use this policy to determine which actors may be added to featured collections, but one always has to check if the approval for a specific featured collection was really given. See the next section for details.

Note that this is modeled closely after interaction policies for quote posts (see FEP-044f).

"},{"location":"fep/7aa9/#obtaining-consent","title":"Obtaining consent","text":"

While interaction policies signal an actor's general preferences any attempt to include an actor in a featured collection MUST ask for consent explicitly.

To do so a new activity type FeatureRequest is introduced. It has two mandatory properties:

When adding an actor to a featured collection the owner of said collection MUST send a FeatureRequest activity to the actor that is about to be added.

Example FeatureRequest:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/7aa9\"\n  ],\n  \"id\": \"https://fedi.example.com/users/alice/featured/23/requests/2\",\n  \"type\": \"FeatureRequest\",\n  \"object\": \"https://other.example.com/users/bob\",\n  \"instrument\": \"https://fedi.example.com/users/alice/featured/23\"\n}\n

In response the actor can either issue an Accept or a Reject activity. In case of automatic approval, those can be issued immediately. In case of manual approval, a user needs to be notified and asked before the answer can be sent.

In both cases, Accept or Reject, the object of the activity is the FeatureRequest. In case of an Accept the activity MUST also include a result property pointing to a FeatureAuthorization.

Example Accept activity:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\"\n  ],\n  \"type\": \"Accept\",\n  \"to\": \"https://fedi.example.com/users/alice\",\n  \"actor\": \"https://other.example.com/users/bob\",\n  \"object\": \"https://fedi.example.com/users/alice/featured/23/requests/2\",\n  \"result\": \"https://other.example.com/users/bob/stamps/1024\"\n}\n

Example Reject activity:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\"\n  ],\n  \"type\": \"Reject\",\n  \"to\": \"https://fedi.example.com/users/alice\",\n  \"actor\": \"https://other.example.com/users/bob\",\n  \"object\": \"https://fedi.example.com/users/alice/featured/23/requests/2\",\n}\n

When the server that issued the FeatureRequest receives an Accept it SHOULD add a new FeaturedItem to the FeaturedCollection in which case the featureAuthorization property MUST include the result of the Accept activity.

Example FeaturedItem resulting from the Accept above:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/7aa9\"\n  ],\n  \"id\": \"https://fedi.example.com/users/alice/featured/23/items/2\",\n  \"type\": \"FeaturedItem\",\n  \"object\": \"https://other.example.com/users/bob\",\n  \"featureAuthorization\": \"https://other.example.com/users/bob/stamps/1024\",\n  \"published\": \"2025-08-14T12:13:22Z\"\n}\n

In case of a Reject, a new FeaturedItem MUST NOT be created and nothing is added to the FeaturedCollection.

"},{"location":"fep/7aa9/#verification","title":"Verification","text":"

The FeatureAuthorization obtained through the Accept activity as described in the previous section serves as an \"approval stamp\", an object that can be used to verify that approval to be included in a featured collection was given.

A FeatureAuthorization MUST include the following properties:

Example FeatureAuthorization:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://gotosocial.org/ns\",\n    \"https://w3id.org/fep/7aa9\"\n  ],\n  \"id\": \"https://other.example.com/users/bob/stamps/1024\",\n  \"type\": \"FeatureAuthorization\",\n  \"interactingObject\": \"https://fedi.example.com/users/alice/featured/23\",\n  \"interactionTarget\": \"https://other.example.com/users/bob\"\n}\n

When processing a FeaturedCollection from a remote server the FeatureAuthorization of every FeaturedItem MUST be validated. If it is missing, cannot be resolved or the hosting service does not match the actor's the item MUST be ignored or removed from the collection before it is being displayed to users.

"},{"location":"fep/7aa9/#revocation","title":"Revocation","text":"

Actor's can opt out of being featured after the fact. In that case they MUST issue an Delete activity with the FeatureAuthorization as object.

Example Delete activity:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\"\n  ],\n  \"type\": \"Delete\",\n  \"to\": \"https://fedi.example.com/users/alice\",\n  \"actor\": \"https://other.example.com/users/bob\",\n  \"object\": {\n    \"id\": \"https://other.example.com/users/bob/stamps/1024\",\n    \"type\": \"FeatureAuthorization\",\n    \"interactingObject\": \"https://fedi.example.com/users/alice/featured/23\",\n    \"interactionTarget\": \"https://other.example.com/users/bob\"\n  }\n}\n

When a Delete activity for a FeatureAuthorization is received the affected FeaturedItem MUST be removed from the FeaturedCollection.

"},{"location":"fep/7aa9/#moderation","title":"Moderation","text":"

Featured collections can include language or imagery that are against a given server's rules. As such it MUST be possible to report them and to handle reports received.

Just like with other objects, a report is federated as a Flag activity and FeaturedCollection MAY be added to the list of reported objects:

Example activity:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\"\n  ],\n  \"id\": \"https://other.example.com/reports/17324\",\n  \"type\": \"Flag\",\n  \"actor\": \"https://other.example.com/actor\",\n  \"to\": \"https://fedi.example.com/users/alice\",\n  \"content\": \"Inappropriate language in collection description\",\n  \"objects\": [\n    \"https://fedi.example.com/users/alice\",\n    \"https://fedi.example.com/users/alice/featured/23\"\n  ]\n}\n
"},{"location":"fep/7aa9/#implementation-guidelines","title":"Implementation Guidelines","text":"

The mechanisms described offer a lot of flexibility and thus can lead to some complexity in implementations. But implementations do not have to be complex to work. There is a spectrum of possibilities.

At the lower end of that spectrum sit implementations that do not want to offer their users any control or agency at all. If you think it should always be possible for users to be featured, you can add the same interaction policy to every actor and simply always return positive authorizations. This can be a fully automated process with little overhead.

Similarly an implementation could decide to never allow their users to be added. So either no, or a special interaction policy can be included and authorizations always denied.

At the other end of the spectrum lie implementations that offer their users full flexibility. This would include fine-grained settings for both manual and automatic approval, leading to complex interaction policies. And for manual approval they would probably need a special UI notifying users of a request to be featured with affordances to either accept or deny that request.

Of course there is a lot of middle ground here. A reasonable approach that sits somewhat in the middle could use automatic approval only and offer users a single setting for their preference with a handful of options to chose from. To make up for the lack of manual approval, removing oneself from a featured collection could be made very easy.

"},{"location":"fep/7aa9/#implementations","title":"Implementations","text":""},{"location":"fep/7aa9/#references","title":"References","text":""},{"location":"fep/7aa9/#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/7b29/","title":"FEP-7b29: Federated Signup Requests","text":"Authors Status Date received Tracking issue Discussions Repository Ben Pate \\<@benpate@mastodon.social>, Dan Supernault \\<@dansup@mastodon.social> DRAFT 2026-03-26 #822 Discussions codeberg"},{"location":"fep/7b29/#summary","title":"Summary","text":"

People don't sign up for a new social website in a vacuum. Instead, this is one step in a larger goal that they are trying to accomplish -- such as following a new account, or liking or replying to a particular post.

But this process is often disjointed and filled with \"dead ends\" in the user experience.

\"Federated Signups\" give websites the tools to create a smooth signup process even when the user is bounced between several websites. It accomplishes this by including standardized query parameters in signup referral links to the user's new Fediverse server. This prevents \"dead ends\" and allows the user's new server to complete the original task that started their signup.

"},{"location":"fep/7b29/#1-requirements","title":"1. Requirements","text":"

The key words \"MUST\", \"SHOULD\", and \"MAY\" are to be interpreted as described in RFC2119.

For the purposes of this document, a \"remote server\" is any website on the Internet where this workflow begins, such as a news or informational website. A \"website visitor\" is a person who is consuming the content on a remote server.

Remote servers refer their website visitors to \"potential home servers\", where they may choose to sign up. When a person creates an account on a potential home server, they become a \"user\" and that potential home server simply becomes their \"home server.\"

"},{"location":"fep/7b29/#2-the-problem","title":"2. The Problem","text":"

Currently, most remote servers methods for their website visitors to stay connected with them after their initial visit. For example, visitors who read an article on Substack and Patreon have the opportunity to \"Follow\" the author of the article and be notified when new articles are published.

This workflow is difficult to achieve for new users of a distributed environment.

For instance, a Fediverse news website (such as WeDistribute.org) hosts its own ActivityPub actors, but does not allow website visitors to create accounts on their website. Instead, publishers like this depend on their visitors following from their own \"home server\" - a Fediverse account on an entirely separate domain.

If the visitor already has a home server, then FEP-3b86 Activity Intents provides a smooth workflow for existing Fediverse users to follow.

But this process breaks down if the website visitor does not already have a Fediverse account. At best, publishers can recommend a Fediverse server for visitors to join, but even still, the visitor's original goal is lost. There is no way for them to connect the new account signup back to their original goal, in order to continue following, liking, or replying to the web page on the remote server.

And from the remote server's point of view, that website visitor goes into a black hole. It never finds out if that visitor actually joined and completed their task or not. If the website visitor was half-way into a task -- such as purchasing access to private content -- they may have to start over once they have created an account on their new home server.

If websites are to be gateways for new people to find and join the Fediverse, then we must unify this workflow into a single seamless process, both before and after new users join.

"},{"location":"fep/7b29/#3-federated-signups","title":"3. Federated Signups","text":"

Federated signups define a common way for remote servers to pass metadata to a potential home server when referring website visitors to sign up.

This state information takes the form of optional query parameters passed to the home server's signup page, which allows the home server to complete the user's original goal (for instance, following an author or replying to a post)

This document defines four standard query parameters:

Values for each of these parameters MUST be URL encoded values consistent with URL query parameter standards.

These parameters are merely workflow requests from the remote server to the potential home server. Workflows defined on remote servers MUST NOT depend on receiving a response from potential home servers.

"},{"location":"fep/7b29/#31-pleasefollow","title":"3.1. please.follow","text":"

If a website visitor wishes to Follow a particular actor on a remote website, but does not yet have a Fediverse identity, then the remote website SHOULD refer them to one or more Fediverse servers where they can establish an account. The link provided by the remote server MAY include a please.follow query parameter.

This parameter MUST be a comma separated list of one or more URLS. The URLs MUST refer to one of these two values:

Once the user has established a new account, the home server SHOULD inspect the list of actors and collections, then provide the new user with the option choose which of them to follow.

Home servers MAY apply additional rules to this list, for instance adding additional recommended accounts to follow, or removing values from the provided list for other policy reasons.

Example referral URL using propose.follow:

https://homeserver.social/signup?please.follow=https://remoteserver.social/@actor-to-follow,https://remoteserver.social/starter-pack-to-follow\n
"},{"location":"fep/7b29/#32-pleaseview","title":"3.2. please.view","text":"

If a website visitor wishes to interact with a particular piece of content on a remote website (i.e. to like, boost, or reply to a web page) but does not yet have a Fediverse identity, then the remote website SHOULD refer them to one or more Fediverse servers where they can establish an account. The link provided by the remote server MAY include a please.view query parameter.

This parameter MUST be a single URL that links to the original content.

Once the user has established a new account, the home server SHOULD look up the ActivityStreams representation of the URL and present it to the user.

Home servers MAY apply additional rules to requested URLs. For instance, if a provided URL belongs to a blocked or banned website or user, then the new home server MAY refuse to display it to the new user.

Example referral URL using please.view:

https://homeserver.social/signup?please.view=https://remoteserver.social/my-latest-article\n
"},{"location":"fep/7b29/#321-pleasehighlight","title":"3.2.1 please.highlight","text":"

As an additional option, when a remote server sends a please.view parameter, it MAY also send a please.highlight parameter. This identifies a single activity (such as Like, Announce, Arrive, etc) for the new home server to feature on the page displayed to new users.

Home servers MAY or MAY NOT use this parameter. It is provided as guidance to the new home server to help the new user complete their original task.

Consider the example of a website visitor who clicks a \"Like\" button on the remote server, and is then forwarded to a new home server and completes the signup process there. When their new home server displays the original resource where they clicked \"Like\", it MAY also highlight the \"Like\" button in some way on its corresponding page. This MAY be by color, size, positioning, or any other method determined by the home server.

"},{"location":"fep/7b29/#33-pleasenotify","title":"3.3. please.notify","text":"

When a remote website refers a visitor to a new home server, it may have incomplete tasks in process that would benefit by knowing that the visitor has successfully created a new account. For example, if the visitor has purchased access to private content, then the remote website may want to link those privileges to the newly-created identity.

The please.notify parameter asks the new home server to send an out-of-band HTTP POST to a URL specified by the remote server.

The URL specified in the please.notify parameter MUST bePercent Encoded.

The URL specified in the please.notify parameter MAY include a single template string value: {id}.

Home servers MAY take actions based on this parameter, or MAY simply disregard it.

To support this parameter, a home server MUST replace this {id} value with the actor id of the newly created actor. This is the requested notification URL. Then, the home server MUST send an HTTP POST request with no body to the notification URL.

"},{"location":"fep/7b29/#40-same-server-signups","title":"4.0. Same Server Signups","text":"

Federated Signups help improve the signup workflow even if the remote server and home server are the same. If a Fediverse-enabled website allows anonymous user signups, it can still provide a smoother user experience by passing the four standard parameters to its own signup pages.

"},{"location":"fep/7b29/#50-instance-choosers","title":"5.0. Instance Choosers","text":"

There are a growing number of \"instance chooser\" websites that help potential new users to navigate the wide number of Fediverse servers to choose from.

\"Instance chooser\" websites SHOULD accept the four standard parameters, and then forward these values on to the signup page for the instance that the user ultimately picks.

This allows remote servers to let their visitors pick a new instance according to the rules provided by the instance chooser, and still request these important signup actions from the selected home server.

"},{"location":"fep/7b29/#60-implementations","title":"6.0. Implementations","text":""},{"location":"fep/7b29/#61-home-servers","title":"6.1 Home Servers","text":"

This is a list of \"home servers\" that publish Activity Intent endpoints.

"},{"location":"fep/7b29/#62-clients","title":"6.2 Clients","text":"

This is a list of client tools that allow end-users to use Activity Intents on remote websites.

"},{"location":"fep/7b29/#references","title":"References","text":""},{"location":"fep/7b29/#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/7d8c/","title":"FEP-7d8c: Documentation: Automation of FEP","text":"Authors Status Date received Tracking issue Discussions Repository Helge @helge@mymath.rocks DRAFT 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:

Automation 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.

"},{"location":"fep/7d8c/#running-tests","title":"Running tests","text":"

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.

"},{"location":"fep/7d8c/#building-the-website","title":"Building the website","text":"

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:

"},{"location":"fep/7d8c/#automatic-deployment","title":"Automatic deployment","text":"

See website.yml.

"},{"location":"fep/7d8c/#changes","title":"Changes","text":""},{"location":"fep/7d8c/#references","title":"References","text":""},{"location":"fep/7d8c/#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/82f6/","title":"FEP-82f6: Actor statuses","text":"Authors Status Date received Tracking issue Discussions Repository Gregory Klyushnikov activitypub@grishka.me DRAFT 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:

"},{"location":"fep/82f6/#actor-fields","title":"Actor fields","text":"

The following two OPTIONAL fields are added to actors:

"},{"location":"fep/82f6/#activities","title":"Activities","text":""},{"location":"fep/82f6/#creating-a-status-update","title":"Creating a status update","text":"

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.

"},{"location":"fep/82f6/#clearing-the-status","title":"Clearing the status","text":"

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.

"},{"location":"fep/82f6/#deleting-the-status","title":"Deleting the status","text":"

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.

"},{"location":"fep/82f6/#other-activities-in-relation-to-statuses","title":"Other activities in relation to statuses","text":"

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.

"},{"location":"fep/82f6/#context","title":"Context","text":"

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":""},{"location":"fep/82f6/#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/844e/","title":"FEP-844e: Capability discovery","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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 objects, each containing the following properties:

The order of objects in the array is not important.

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":""},{"location":"fep/844e/#references","title":"References","text":""},{"location":"fep/844e/#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/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.com DRAFT 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.

"},{"location":"fep/888d/#acknowledgements","title":"Acknowledgements","text":"

(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.

"},{"location":"fep/888d/#requirements","title":"Requirements","text":"

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:

At minimum, the following redirects SHOULD resolve as follows:

Additionally, the following MAY resolve:

"},{"location":"fep/888d/#mapping-w3idorgfep-to-fediversefep-on-codeberg","title":"Mapping w3id.org/fep to fediverse/fep on Codeberg","text":"

At 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.

"},{"location":"fep/888d/#example","title":"Example","text":"

(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.

"},{"location":"fep/888d/#example-using-paths","title":"Example using paths","text":"

(This section is non-normative.)

For example, say we wanted to define the following terms within the current FEP-888d:

The 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:

"},{"location":"fep/888d/#defining-terms-in-a-machine-readable-way","title":"Defining terms in a machine-readable way","text":"

(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:

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:

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:

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:

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.

"},{"location":"fep/888d/#using-the-python-script","title":"Using the python script","text":"

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":""},{"location":"fep/888d/#algorithm","title":"Algorithm","text":""},{"location":"fep/888d/#usage","title":"Usage","text":""},{"location":"fep/888d/#example-terms-defined-by-this-fep","title":"Example terms defined by this FEP","text":"

(This section is non-normative.)

Using the same fictitious terms from the above examples:

We 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:

SomeType 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
\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":""},{"location":"fep/888d/#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/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.

"},{"location":"fep/8967/#link-preview","title":"Link preview","text":"

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:

Consumers 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>.

"},{"location":"fep/8967/#alternative-approaches","title":"Alternative approaches","text":"

(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.

"},{"location":"fep/8967/#implementations","title":"Implementations","text":""},{"location":"fep/8967/#references","title":"References","text":""},{"location":"fep/8967/#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/","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.org DRAFT 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.

"},{"location":"fep/8a8e/#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 context of attributes: Attributes that have no context prefix are to be interpreted as being in the https://www.w3.org/ns/activitystreams context.

"},{"location":"fep/8a8e/#object-type-event","title":"Object Type Event","text":"

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:

{\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.

"},{"location":"fep/8a8e/#time-zone","title":"Time zone","text":"

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:

"},{"location":"fep/8a8e/#physical","title":"Physical","text":"

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.

"},{"location":"fep/8a8e/#rsvp-attendee-management","title":"RSVP (Attendee Management)","text":""},{"location":"fep/8a8e/#not-handling-rsvp","title":"Not handling RSVP","text":"

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.

"},{"location":"fep/8a8e/#signaling-how-joins-are-handled","title":"Signaling how joins are handled","text":"

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.

"},{"location":"fep/8a8e/#showing-rsvp-status-via-activitypub","title":"Showing RSVP status via ActivityPub","text":"

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.

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.

"},{"location":"fep/8a8e/#event-banner-and-poster-images","title":"Event Banner and Poster Images","text":""},{"location":"fep/8a8e/#flyer","title":"Flyer","text":"

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.

"},{"location":"fep/8a8e/#banner","title":"Banner","text":"

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).

"},{"location":"fep/8a8e/#organizers-collection","title":"Organizers Collection","text":"

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.

"},{"location":"fep/8a8e/#fallback-to-attributedto","title":"Fallback to 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.

"},{"location":"fep/8a8e/#upcoming-events-collection-for-actors","title":"Upcoming Events Collection for Actors","text":"

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:

"},{"location":"fep/8a8e/#terms","title":"Terms","text":"

Note that the terms joinMode and externalParticipationUrl are compatible with and also leaned on the extensions defined by Mobilizon.

displayEndTime 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
\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. 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
\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\"} ] }

"},{"location":"fep/8a8e/EventCancelled/","title":"EventCancelled","text":"

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.\" } ] }

"},{"location":"fep/8a8e/attendees/","title":"attendees","text":"

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.\" } ] } }

"},{"location":"fep/8a8e/category/","title":"category","text":"

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 }

"},{"location":"fep/8a8e/eventStatus/","title":"eventStatus","text":"

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/\" }

"},{"location":"fep/8a8e/isBannerImage/","title":"isBannerImage","text":"

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 } ] }

"},{"location":"fep/8a8e/joinMode/","title":"joinMode","text":"

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.\" } ] } }

"},{"location":"fep/8a8e/previousStartTime/","title":"previousStartTime","text":"

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\" ] }

"},{"location":"fep/8a8e/timezone/","title":"timezone","text":"

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 }

"},{"location":"fep/8a8e/upcomingEvents/","title":"upcomingEvents","text":"

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\" } ] } }

"},{"location":"fep/8b32/","title":"FEP-8b32: Object Integrity Proofs","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social 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 offline verification 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.

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.

Objects identified using fragment identifiers SHOULD NOT contain integrity proofs.

Objects SHOULD NOT contain more than one integrity proof.

"},{"location":"fep/8b32/#proof-verification","title":"Proof verification","text":"

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 a verifier encounters an integrity proof that uses a verification method or cryptosuite that it doesn't support, it SHOULD ignore the proof and try other authentication methods.

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:

"},{"location":"fep/8b32/#backward-compatibility","title":"Backward compatibility","text":"

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.

"},{"location":"fep/8b32/#security-considerations","title":"Security considerations","text":"

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.

Integrity proofs can be repudiated by rotating the verification method.

"},{"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":""},{"location":"fep/8b32/#implementations","title":"Implementations","text":""},{"location":"fep/8b32/#use-cases","title":"Use cases","text":""},{"location":"fep/8b32/#references","title":"References","text":""},{"location":"fep/8b32/#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/8c13/","title":"FEP-8c13: Context-Authority Routing with Object Integrity Proofs for Restricted Threads","text":"Authors Status Date received Discussions Repository Dmitry Skavish skavish@gmail.com DRAFT 2026-01-16 Discussions codeberg"},{"location":"fep/8c13/#summary","title":"Summary","text":"

In ActivityPub today, a reply to a \"followers-only\" post is delivered only to the replier's followers. Everyone else in the conversation - including people who follow the original author - never sees it, so the thread fragments into inconsistent partial views.

This proposal fixes that by giving every restricted thread a single coordinating server: the Context Authority, which is the server that hosts the thread's root post. All replies, reactions, edits, and deletes for the thread are sent to that one server, which validates them and fans them out to everyone currently allowed to see the thread. Because all traffic flows through one authority, every participant converges on the same view.

"},{"location":"fep/8c13/#how-it-works-end-to-end","title":"How it works, end to end","text":"
  1. Every thread has a Context Object - a dereferenceable collection (per FEP-7888) that represents the thread and is hosted by the root author's server. Its URI is the context value carried on posts in the thread.
  2. A sender replies normally. They use ordinary to/cc addressing (e.g. the author and their followers) and include the thread's context. They do not put the context URI in to/cc. They deliver the reply to the root author's inbox.
  3. The Context Authority routes it. The root author's server recognizes itself as the Context Authority for that context, validates the reply (authorization + addressing rules + integrity), stores it in the Context Object, and forwards it to everyone currently authorized.
  4. \"Currently authorized\" is the Thread Policy - the to/cc of the root post as the Context Authority last published it. If the author later tightens or loosens visibility, the Context Authority republishes the root object via Update, and future activities are routed to the new audience.
"},{"location":"fep/8c13/#why-integrity-proofs-fep-8b32","title":"Why integrity proofs (FEP-8b32)","text":"

When the Context Authority forwards a reply, it may need to rewrite the reply's to/cc so that delivery matches the current Thread Policy (for example, the audience changed since the reply was written). Plain HTTP Signatures only prove who delivered a message, not who wrote it, and they break the moment an intermediary touches the payload.

So each sender attaches an Author Proof: a Data Integrity proof (FEP-8b32) computed over the activity with to/cc removed. This applies to every kind of context activity - a reply, a like, a reaction, an announce, an edit, a delete - not just posts. Excluding the addressing lets the Context Authority rewrite delivery without invalidating the author's signature over the content, while recipients can still verify that the content was written by the claimed author and not altered in transit. An optional Forwarding Proof lets the Context Authority additionally sign the addressing it chose, so recipients can verify the routing offline instead of trusting only the transport.

"},{"location":"fep/8c13/#backward-compatibility","title":"Backward compatibility","text":"

The context field and the proofs are additive: servers that don't understand them ignore them harmlessly. Such legacy servers can still receive forwarded replies and reply into a thread (their reply reaches the directly addressed author), but they cannot originate or relay integrity-protected context fan-out.

"},{"location":"fep/8c13/#applicability-across-visibility-classes","title":"Applicability across visibility classes","text":"

The same machinery serves every conversation visibility. For direct threads (addressed to explicit actors) and followers-only / private-group threads, the audience is bounded and the Context Authority forwards to it directly. For public threads the audience is unbounded, so the Context Authority pushes to the thread's participants (everyone who has replied or reacted) plus the root author's followers, and serves all other readers by letting them pull the thread from the Context Object. In every class the participants - including legacy servers - converge on the same complete reply tree. See Thread Visibility Classes.

"},{"location":"fep/8c13/#motivation","title":"Motivation","text":""},{"location":"fep/8c13/#the-reply-visibility-trap","title":"The reply visibility trap","text":"

In current ActivityPub deployments, followers-only conversations fragment:

"},{"location":"fep/8c13/#lack-of-object-level-integrity","title":"Lack of object-level integrity","text":"

HTTP Signatures authenticate transport, not content. When inbox forwarding is used, recipients cannot verify that the object content they receive was authored by the claimed actor and was not modified by an intermediate server. FEP-8b32 introduces per-object integrity proofs that decouple content verification from transport; this proposal builds on them.

A server that does not implement FEP-8b32 cannot participate in context fan-out as a sender (its activities will not be forwarded), but may still receive forwarded activities and reply as a legacy endpoint.

"},{"location":"fep/8c13/#related-work","title":"Related work","text":"

FEP-1b12 (Group Federation) established the pattern of a central actor (the Group) receiving activities and redistributing them to members, which inspired the Context Authority model. FEP-1b12 targets explicit group membership rather than ad-hoc conversations.

FEP-171b (Conversation Containers) defines a model where a single conversation owner distributes approved activities to participants via wrapper activities. That approach and this proposal address the same class of problems with different authority and delivery semantics.

Dimension FEP-8c13 (Context Authority Routing) FEP-171b (Conversation Containers) Core mechanism Native activities routed via context field Owner republishes activities via Add wrapper Authority model Context Authority validates and forwards eligible replies Conversation owner explicitly approves and commits events Commit semantics Rule-based inclusion (authorization + integrity) Explicit owner approval (Add) Wire format Standard ActivityPub activities Add(Activity) wrapper required Integrity model Mandatory Data Integrity proof for context fan-out Proof optional; origin-fetch fallback allowed Mixed environments Designed to degrade safely with legacy servers Assumes shared container semantics Adoption surface Requires FEP-8b32; reuses existing AP fields Higher: introduces new behavioral contract"},{"location":"fep/8c13/#terminology","title":"Terminology","text":""},{"location":"fep/8c13/#data-model","title":"Data Model","text":""},{"location":"fep/8c13/#context-object","title":"Context Object","text":"

The context property of an ActivityPub object MUST be an IRI identifying a dereferenceable Context Object: an authorized GET returns an ActivityPub representation, while unauthorized requests MAY receive 401/403 under the access control below.

The context property MUST be treated as a first-class payload reference, not an opaque identifier. The Context Authority MUST resolve it to obtain authoritative metadata about the thread; other servers SHOULD resolve it when they need backfill or authorization decisions.

The Context Object is authoritative for thread history indexing and backfill discovery only. Authorization (\"who may receive content\") is defined separately by the root object's current to/cc (the Thread Policy) plus local policy. The Context Object does not encode membership; it indexes the thread's Context Activities for backfill and convergence.

A Context Object URI MUST be stable and SHOULD be derivable. A simple, recommended construction is https://{context-authority}/contexts/{topLevelPostId}, which allows deterministic discovery of the context given the root object.

"},{"location":"fep/8c13/#root-authority-alignment-normative","title":"Root Authority Alignment (Normative)","text":"

For a context to be valid under this FEP, the Context Authority MUST be the same instance that hosts the root object (the object whose id is the canonical top-level post for the thread), and MUST be able to authoritatively publish Update activities for that root object.

This ensures that the entity controlling the Context Object is the same entity that can change the Thread Policy via root object updates.

The Context Object:

Example:

{\n  \"id\": \"https://alice.example/contexts/12345\",\n  \"type\": \"OrderedCollection\",\n  \"totalItems\": 5,\n  \"first\": \"https://alice.example/contexts/12345?page=1\"\n}\n
"},{"location":"fep/8c13/#collection-contents-normative","title":"Collection Contents (Normative)","text":"

When the context resolves to an OrderedCollection, that collection:

"},{"location":"fep/8c13/#access-control-for-limited-visibility-contexts","title":"Access Control for Limited-Visibility Contexts","text":"

For restricted conversations (followers-only, direct, etc.), the Context Authority MAY allow dereferencing of the Context Object and its collection pages, subject to strict access control. Authorization for such dereferencing MUST be evaluated by the Context Authority using the current Thread Policy plus local policy.

A Context Authority MAY decline remote dereferencing entirely (always returning 401/403) and rely exclusively on inbox delivery and forwarding for propagation, while still satisfying the dereferenceability requirement for local processing and authorized local actors.

These access controls apply both to the Context Object itself and to any collection pages or backfill endpoints that enumerate its Context Activities.

"},{"location":"fep/8c13/#authenticated-context-dereference-restricted-contexts","title":"Authenticated Context Dereference (Restricted Contexts)","text":"

Dereferencing a restricted Context Object MUST be authenticated by an actor-bound signature (authorized fetch); instance-only signatures MUST be rejected. Because federation trust is instance-mediated this cannot guarantee user-scoped enforcement - a server may proxy access to its own users - so a Context Authority MAY additionally require an actor authorized under the current Thread Policy, with logging, rate limits, and auditing. This is distinct from the instance-actor authentication used for forwarded deliveries.

"},{"location":"fep/8c13/#context-activities-and-integrity-proofs","title":"Context Activities and Integrity Proofs","text":"

When a Context Activity is ingested, its context reference MUST be resolved to the Context Object and associated with it, not with the immediate parent (inReplyTo) alone. Servers MUST NOT treat context as purely informational; it defines authoritative thread context for history and lifecycle.

This applies to every activity type that participates in the thread - Create, Update, Delete, Like, EmojiReact, Announce, Undo, etc. - not only to reply posts. Many of these (e.g. Like, EmojiReact, Announce) carry their target as an IRI in object rather than an embedded object; for those, context and the Author Proof are carried on the activity itself.

When generating a Context Activity for an object with a resolvable context, implementations:

"},{"location":"fep/8c13/#effective-context-iri-normative","title":"Effective Context IRI (Normative)","text":"

If both a legacy Linked Data signature and a Data Integrity proof are present, implementations MUST ignore the legacy signature for object integrity.

All Context Activities intended for context fan-out MUST carry a valid Author Proof.

"},{"location":"fep/8c13/#json-ld-context-and-extension-terms-normative","title":"JSON-LD Context and Extension Terms (Normative)","text":"

authorProof and forwardingProof are not defined by the ActivityStreams 2.0 context. Activities carrying them SHOULD include an @context defining them, alongside the Data Integrity context from the deployment's FEP-8b32 profile, so JSON-LD processors do not drop them:

\"@context\": [\n  \"https://www.w3.org/ns/activitystreams\",\n  \"https://w3id.org/security/data-integrity/v1\",\n  \"https://w3id.org/fep/8c13\"\n]\n

The https://w3id.org/fep/8c13 term context (provisional; to be assigned on publication) defines authorProof and forwardingProof as DataIntegrityProof containers. The Data Integrity context MUST match the one used by the deployment's FEP-8b32 profile.

Because eddsa-jcs-2022 canonicalizes the JSON document with JCS - including @context - signers and verifiers MUST use the same @context; it is part of the Author Proof signed input and is not among the excluded fields. Implementations that do not perform JSON-LD processing MAY treat authorProof/forwardingProof as plain JSON members but MUST still reproduce the exact @context for canonicalization. The wire examples elsewhere in this document show only the ActivityStreams context for brevity; conforming activities include the full @context above.

"},{"location":"fep/8c13/#author-proof-canonicalization-normative","title":"Author Proof Canonicalization (Normative)","text":"

The Author Proof uses the canonicalization and verification rules of FEP-8b32, with one addition defined here: certain addressing and forwarding fields are excluded from the signed input. The Author Proof is carried in a dedicated authorProof field (not proof); for the FEP-8b32 process, authorProof is the proof container.

Before canonicalization, the signer and all verifiers MUST remove the following fields from the activity and from any embedded object: to, cc, and forwardingProof. The legacy Linked Data signature field MUST also be excluded if present. No other fields may be excluded for Author Proof verification under this FEP. The removed fields are treated as not part of the signed input.

The proof being verified is excluded as defined by FEP-8b32 (verifiers canonicalize the document without the authorProof value they are verifying, and process its proof options without proofValue); this FEP does not alter that base rule, only the field name.

"},{"location":"fep/8c13/#relationship-to-fep-8b32-normative","title":"Relationship to FEP-8b32 (Normative)","text":"

The Author Proof reuses the FEP-8b32 proof envelope and cryptosuite (e.g. eddsa-jcs-2022), but it is computed over a transformed input: the activity with to, cc, and forwardingProof removed. This exclusion is deliberate - it is precisely what lets the Context Authority rewrite delivery addressing to match the current Thread Policy without invalidating the author's signature over the content.

FEP-8b32 and the eddsa-jcs-2022 cryptosuite sign the whole document (minus the proof being verified) and have no field-exclusion step. So an Author Proof is intentionally not a whole-document FEP-8b32 proof. To keep the two from being confused, the Author Proof is carried in a dedicated authorProof field rather than the standard proof field. This is what keeps the design compatible with FEP-8b32:

In short, authorProof and proof are independent layers: authorProof provides author-authenticity that survives address rewiring, while proof (if present) provides ordinary whole-document integrity. A deployment may use either or both.

Because a whole-document proof signs to/cc (which the Author Proof deliberately excludes), such a proof is invalidated whenever the Context Authority rewires addressing during forwarding. For context-routed activities the Author Proof is therefore the authoritative content-integrity check. A whole-document proof on a context-routed activity or its embedded object MUST NOT be relied upon to survive forwarding, and recipients MUST NOT reject a context-routed activity solely because such a proof fails to verify; rejection is governed by the Author Proof and the Routing Decision Matrix. Senders that need content to remain verifiable across forwarding SHOULD rely on the Author Proof rather than a whole-document proof.

"},{"location":"fep/8c13/#forwarding-proof-canonicalization-normative-optional","title":"Forwarding Proof Canonicalization (Normative, Optional)","text":"

The Forwarding Proof input MUST be a JSON object with exactly these keys:

If the activity embeds its object, object is the embedded object's id; if object is an IRI, object is that IRI; if neither is available, a Forwarding Proof MUST NOT be generated.

Absent to/cc are treated as empty arrays. If present, they MUST be arrays of IRI strings; any other form (string singleton, object, non-IRI value) MUST cause Forwarding Proof generation or verification to fail. The to and cc arrays MUST be normalized by removing duplicates (set semantics) and sorting lexicographically by Unicode code points of the IRI string.

The resulting JSON object MUST be serialized using JSON Canonicalization Scheme (JCS, RFC 8785) before generating the Data Integrity proof.

"},{"location":"fep/8c13/#proof-placement-normative","title":"Proof Placement (Normative)","text":""},{"location":"fep/8c13/#integrity-requirements-normative","title":"Integrity Requirements (Normative)","text":""},{"location":"fep/8c13/#example-incoming-reply-sender-context-authority","title":"Example: Incoming Reply (Sender \u2192 Context Authority)","text":"

The sender addresses the reply to match the Thread Policy (followers-only here). The Author Proof is computed with to/cc excluded from the canonicalized input.

{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"id\": \"https://bob.example/activities/98765\",\n  \"type\": \"Create\",\n  \"actor\": \"https://bob.example/u/bob\",\n  \"to\": [\"https://alice.example/u/alice/followers\"],\n  \"cc\": [\"https://alice.example/u/alice\"],\n  \"object\": {\n    \"id\": \"https://bob.example/posts/98765\",\n    \"type\": \"Note\",\n    \"attributedTo\": \"https://bob.example/u/bob\",\n    \"inReplyTo\": \"https://alice.example/posts/12345\",\n    \"context\": \"https://alice.example/contexts/12345\",\n    \"content\": \"Hi Alice, I saw your followers-only post.\",\n    \"to\": [\"https://alice.example/u/alice/followers\"],\n    \"cc\": [\"https://alice.example/u/alice\"]\n  },\n  \"authorProof\": {\n    \"type\": \"DataIntegrityProof\",\n    \"cryptosuite\": \"eddsa-jcs-2022\",\n    \"created\": \"2026-01-16T15:20:45Z\",\n    \"verificationMethod\": \"https://bob.example/u/bob#main-key\",\n    \"proofPurpose\": \"assertionMethod\",\n    \"proofValue\": \"z3FXQjecWuf...\"\n  }\n}\n
"},{"location":"fep/8c13/#example-forwarded-reply-context-authority-recipient","title":"Example: Forwarded Reply (Context Authority \u2192 Recipient)","text":"

The Context Authority forwards the activity (no rewiring needed in this example). The Author Proof is unchanged (it was computed without addressing fields). The optional Forwarding Proof is shown, signed by the Context Authority's instance actor.

{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"id\": \"https://bob.example/activities/98765\",\n  \"type\": \"Create\",\n  \"actor\": \"https://bob.example/u/bob\",\n  \"to\": [\"https://alice.example/u/alice/followers\"],\n  \"cc\": [\"https://alice.example/u/alice\"],\n  \"object\": {\n    \"id\": \"https://bob.example/posts/98765\",\n    \"type\": \"Note\",\n    \"attributedTo\": \"https://bob.example/u/bob\",\n    \"inReplyTo\": \"https://alice.example/posts/12345\",\n    \"context\": \"https://alice.example/contexts/12345\",\n    \"content\": \"Hi Alice, I saw your followers-only post.\",\n    \"to\": [\"https://alice.example/u/alice/followers\"],\n    \"cc\": [\"https://alice.example/u/alice\"]\n  },\n  \"authorProof\": {\n    \"type\": \"DataIntegrityProof\",\n    \"cryptosuite\": \"eddsa-jcs-2022\",\n    \"created\": \"2026-01-16T15:20:45Z\",\n    \"verificationMethod\": \"https://bob.example/u/bob#main-key\",\n    \"proofPurpose\": \"assertionMethod\",\n    \"proofValue\": \"z3FXQjecWuf...\"\n  },\n  \"forwardingProof\": {\n    \"type\": \"DataIntegrityProof\",\n    \"cryptosuite\": \"eddsa-jcs-2022\",\n    \"created\": \"2026-01-16T15:21:00Z\",\n    \"verificationMethod\": \"https://alice.example/actor#main-key\",\n    \"proofPurpose\": \"assertionMethod\",\n    \"proofValue\": \"z4HYRkemXvg...\"\n  }\n}\n
"},{"location":"fep/8c13/#example-like-non-create-activity","title":"Example: Like (non-Create activity)","text":"

Context routing is not limited to posts. Likes, reactions, announces, edits, and deletes are Context Activities too. A Like carries its target as an IRI in object (no embedded object), so context and the authorProof are carried on the activity. The Author Proof is still computed with to/cc excluded.

{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"id\": \"https://bob.example/activities/55512\",\n  \"type\": \"Like\",\n  \"actor\": \"https://bob.example/u/bob\",\n  \"to\": [\"https://alice.example/u/alice/followers\"],\n  \"cc\": [\"https://alice.example/u/alice\"],\n  \"context\": \"https://alice.example/contexts/12345\",\n  \"object\": \"https://alice.example/posts/12345\",\n  \"authorProof\": {\n    \"type\": \"DataIntegrityProof\",\n    \"cryptosuite\": \"eddsa-jcs-2022\",\n    \"created\": \"2026-01-16T15:25:10Z\",\n    \"verificationMethod\": \"https://bob.example/u/bob#main-key\",\n    \"proofPurpose\": \"assertionMethod\",\n    \"proofValue\": \"z5KZ8nQ2tps...\"\n  }\n}\n
"},{"location":"fep/8c13/#addressing-and-context-routing-semantics","title":"Addressing and Context-Routing Semantics","text":""},{"location":"fep/8c13/#signaling-intent-to-use-context-routing","title":"Signaling Intent to Use Context Routing","text":"

When constructing a Context Activity for context-audience routing, the sender:

When requesting context routing, senders MUST deliver the Context Activity to the inbox (or endpoints.sharedInbox) of the root object's author - i.e. the Context Authority instance per Root Authority Alignment. The root object's attributedTo actor's inbox is the discovery target.

A receiving server treats an activity as requesting context routing only when both:

This specification requires no capability discovery or negotiation. Routing intent is expressed solely by the presence of an Effective Context IRI in the payload; whether routing occurs additionally depends on the receiver being the Context Authority and on the validation requirements below.

"},{"location":"fep/8c13/#normative-meaning-of-effective-context-iri","title":"Normative Meaning of Effective Context IRI","text":"

If an activity carries an Effective Context IRI and the receiving server is the Context Authority for it, the activity is requesting (a) ingestion into the thread as a Context Activity, and (b) potential forwarding to authorized recipients, subject to the validation and integrity requirements below.

The presence of an Effective Context IRI is necessary but not sufficient for context routing. Eligibility is determined exclusively by a valid Author Proof and authorization checks - never by the perceived capabilities of the sender's server.

No capability negotiation (normative):

"},{"location":"fep/8c13/#reply-visibility-rules","title":"Reply Visibility Rules","text":"

The Context Authority stores the Thread Policy as the root object's to/cc. An activity's effective visibility is determined by comparing its Effective Addressing against the root object's current to/cc.

These rules are written in terms of replies for readability, but apply to all Context Activities (replies, reactions, edits, deletes, etc.).

Inherited (\"follow-post\") addressing: Effective Addressing exactly equal (set-equality, order-insensitive) to the root object's current to/cc. This signals \"same visibility as thread.\"

Narrowed addressing: An activity MAY narrow visibility using any addressing forms permitted by ActivityPub (individual actor IRIs and/or collections), provided the resulting Effective Addressing is not more permissive than the current Thread Policy and is a subset of the Authorized Recipient Set under the current Thread Policy and local policy.

The Context Authority MUST evaluate whether the narrowed Effective Addressing is a subset of the Authorized Recipient Set, including membership in collections it controls (e.g. followers). If it cannot determine subset safety (e.g. unknown external collection semantics), it MUST treat the activity as more permissive and MUST reject it for context routing.

For narrowed activities, the Context Authority MUST preserve the sender's narrowed Effective Addressing when forwarding, except to remove recipients no longer authorized under the current Thread Policy and local policy. It MUST NOT widen a narrowed activity to the full Thread Policy.

Prohibited addressing: An activity MUST NOT be more permissive than the Thread Policy. Violations MUST be rejected by the Context Authority.

Recipient set equality: Two addressing fields are equal if they contain the same IRIs as a set, ignoring order and duplicates. Implementations MUST canonicalize lists before comparison by removing duplicates and sorting lexicographically by Unicode code points. Comparison is string-based only: implementations MUST NOT dereference IRIs or apply semantic equivalence when comparing recipients.

"},{"location":"fep/8c13/#thread-visibility-classes","title":"Thread Visibility Classes","text":"

This FEP applies to conversations of any visibility, but it separates two notions that the rest of this document treats together for restricted threads:

The Delivery Target is a set of delivery inboxes, not an addressing instruction. Forwarded to/cc continue to follow the Thread Policy and the Address Rewiring rules; the Context Authority MUST NOT enumerate the Delivery Target into the forwarded to/cc (see Security Considerations).

Three visibility classes are distinguished by the root object's addressing:

Class Thread Policy (root to/cc) Authorized Recipient Set Delivery Target (push) Backfill (pull) Direct Explicit actor IRIs The listed actors (enumerable) = Authorized Recipient Set Authorized actors Followers-only / private group A collection (e.g. followers) Collection members (may be non-enumerable) = Authorized Recipient Set Authorized actors Public Includes as:Public Everyone (unbounded) Participation Set + root author's followers Anyone (public Context Object)

Followers-only and private-group threads are the default case described throughout this document: authorization and delivery coincide, and the Context Authority forwards to the Authorized Recipient Set, expanding collections it controls. The two subsections below add the rules for the direct and public classes.

"},{"location":"fep/8c13/#direct-conversations-normative","title":"Direct Conversations (Normative)","text":"

When the Thread Policy is a set of explicit actor IRIs (a direct message or an enumerated private group), the Authorized Recipient Set and the Delivery Target are both that explicit actor set, so subset checks under the Reply Visibility Rules are exact: a reply addressing any actor outside the set - or as:Public - is more permissive than the Thread Policy and MUST be rejected, which is what stops a participant from leaking the thread by replying broadly. Membership is changed by the root author updating the root object's to/cc, propagated via Root Policy Update Propagation.

The Author Proof is the primary defense against the Context Authority fabricating a message attributed to another participant; recipients MUST reject forwarded activities whose Author Proof does not verify against the activity actor. Routing through one instance still lets it withhold or reorder messages (forgery is prevented, censorship is not), and this FEP provides no end-to-end confidentiality - direct content is visible to the Context Authority in cleartext, as with ordinary ActivityPub direct messages.

"},{"location":"fep/8c13/#example-direct-reply","title":"Example: Direct Reply","text":"

Alice's root post is addressed to the explicit member set {bob, carol} (the Thread Policy). Bob replies; his Effective Addressing {alice, carol} is a subset, so the Context Authority forwards it to Alice and Carol. Adding a member later is just an Update to the root to/cc, after which the new actor's server backfills from the Context Object.

{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"id\": \"https://bob.example/activities/3002\",\n  \"type\": \"Create\",\n  \"actor\": \"https://bob.example/u/bob\",\n  \"to\": [\n    \"https://alice.example/u/alice\",\n    \"https://carol.example/u/carol\"\n  ],\n  \"object\": {\n    \"id\": \"https://bob.example/posts/3002\",\n    \"type\": \"Note\",\n    \"attributedTo\": \"https://bob.example/u/bob\",\n    \"inReplyTo\": \"https://alice.example/posts/3001\",\n    \"context\": \"https://alice.example/contexts/3001\",\n    \"content\": \"Works for me.\",\n    \"to\": [\n      \"https://alice.example/u/alice\",\n      \"https://carol.example/u/carol\"\n    ]\n  },\n  \"authorProof\": {\n    \"type\": \"DataIntegrityProof\",\n    \"cryptosuite\": \"eddsa-jcs-2022\",\n    \"created\": \"2026-01-16T17:02:11Z\",\n    \"verificationMethod\": \"https://bob.example/u/bob#main-key\",\n    \"proofPurpose\": \"assertionMethod\",\n    \"proofValue\": \"z6PkR2mWq8t...\"\n  }\n}\n
"},{"location":"fep/8c13/#public-conversations-normative","title":"Public Conversations (Normative)","text":"

When the Thread Policy includes the public sentinel (https://www.w3.org/ns/activitystreams#Public, as:Public), every actor is authorized, so the Reply Visibility Rules permit any addressing. A reply MAY still narrow its own visibility (e.g. a followers-only reply to a public post), which the Context Authority MUST preserve and MUST NOT broaden back to Public.

Because the audience is unbounded, delivery is split between push and pull. The Context Authority maintains a Participation Set - the root author plus every actor whose Context Activity it has ingested (optionally limited to authors of Create activities, to curb amplification) - and SHOULD forward each ingested activity to the other participants and to the root author's followers, deduplicated by sharedInbox. The forwarded to/cc stay as the Thread Policy addressing (as:Public plus followers); the Participation Set only selects inboxes and MUST NOT be enumerated into the addressing. Everyone else - readers who never engage - obtains the thread by pull, dereferencing the (publicly readable) Context Object; a new participant backfills the same way on its first reply, then receives the rest by push. On large threads the Context Authority MAY rate-limit, drop high-volume reactions from push, or fall back to pull-only, provided the Context Object stays dereferenceable for backfill.

Because forwarded activities are native (Create, Like, etc.) with ordinary addressing, legacy servers among the participants receive them as normal federated replies and gain a more complete thread with no code changes - which is how this FEP closes the well-known fragmentation of public reply trees. (informative)

"},{"location":"fep/8c13/#example-public-thread-fan-out","title":"Example: Public Thread Fan-Out","text":"

Bob and Carol have already replied to Alice's public post, so the Participation Set is {alice, bob, carol}. Dave replies publicly, delivering to Alice's inbox:

{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"id\": \"https://dave.example/activities/4004\",\n  \"type\": \"Create\",\n  \"actor\": \"https://dave.example/u/dave\",\n  \"to\": [\"https://www.w3.org/ns/activitystreams#Public\"],\n  \"cc\": [\n    \"https://alice.example/u/alice/followers\",\n    \"https://alice.example/u/alice\"\n  ],\n  \"object\": {\n    \"id\": \"https://dave.example/posts/4004\",\n    \"type\": \"Note\",\n    \"attributedTo\": \"https://dave.example/u/dave\",\n    \"inReplyTo\": \"https://alice.example/posts/4000\",\n    \"context\": \"https://alice.example/contexts/4000\",\n    \"content\": \"Jumping in late - great thread.\",\n    \"to\": [\"https://www.w3.org/ns/activitystreams#Public\"],\n    \"cc\": [\n      \"https://alice.example/u/alice/followers\",\n      \"https://alice.example/u/alice\"\n    ]\n  },\n  \"authorProof\": {\n    \"type\": \"DataIntegrityProof\",\n    \"cryptosuite\": \"eddsa-jcs-2022\",\n    \"created\": \"2026-01-16T18:40:03Z\",\n    \"verificationMethod\": \"https://dave.example/u/dave#main-key\",\n    \"proofPurpose\": \"assertionMethod\",\n    \"proofValue\": \"z7QmN4rXc9v...\"\n  }\n}\n
"},{"location":"fep/8c13/#context-authority-processing","title":"Context Authority Processing","text":"

When a Context Authority receives an activity whose Effective Context IRI references a local Context Object, it MUST process it per the steps below. Authorization to submit and to deliver is decided by the Context Authority using the current Thread Policy (root to/cc) plus local rules (blocks, mutes).

"},{"location":"fep/8c13/#validation","title":"Validation","text":"

The Context Authority MUST:

  1. Resolve the Effective Context IRI and confirm it is a Context Object controlled by a local actor (per Root Authority Alignment).
  2. Authenticate the sender via transport (HTTP Signatures or equivalent) and map the request to an ActivityPub actor.
  3. Check sender authorization to submit Context Activities, informed by the current Thread Policy and local policy.
  4. Verify the activity's addressing conforms to the Reply Visibility Rules. Subset evaluation MUST use locally available state for collections it controls (including followers) and any local policy inputs.
  5. Verify the Author Proof: canonicalize with the Author Proof exclusions, then verify against the referenced verification method (which MUST belong to the activity's actor). An Author Proof is verified iff present and successfully verified.

The Context Authority then applies the Routing Decision Matrix. In particular:

"},{"location":"fep/8c13/#transport-authentication-normative","title":"Transport Authentication (Normative)","text":""},{"location":"fep/8c13/#routing-decision-matrix-normative","title":"Routing Decision Matrix (Normative)","text":"

Context-audience routing MUST be integrity-protected: the Context Authority MUST NOT forward any Context Activity unless it carries a verified Author Proof. This is absolute and independent of the sender's implementation or perceived capabilities. The Context Authority MUST determine routing solely from the activity payload and local authorization policy:

Effective Context IRI Author Proof Sender Authorized Required Behavior No Any Any Treat as ordinary ActivityPub delivery Yes Absent Any MAY deliver to directly addressed local recipients; MUST NOT ingest; MUST NOT forward Yes Present but invalid Any MUST reject Yes Present and verified No MUST reject Yes Present and verified Yes MUST ingest and MUST forward (rewiring as needed; MAY add a Forwarding Proof)

No additional server capability checks or negotiations are permitted or required.

"},{"location":"fep/8c13/#ingestion","title":"Ingestion","text":"

For Context Activities from authorized senders with a verified Author Proof, the Context Authority MUST persist the activity/object and MUST add the activity ID to the Context Object's collection.

Activities lacking a valid Author Proof MAY be delivered to directly addressed local recipients (subject to local policy) but MUST NOT be added to the Context Object's collection and are ineligible for context-audience routing.

"},{"location":"fep/8c13/#forwarding","title":"Forwarding","text":"

For Context Activities eligible for context-audience routing, the Context Authority:

Forwarding MUST NOT depend on the perceived compliance or capabilities of recipient servers. Expanding a local follower collection to its remote inboxes does not require rewriting the activity's addressing.

"},{"location":"fep/8c13/#address-rewiring-normative","title":"Address Rewiring (Normative)","text":"

The Context Authority MUST NOT rewrite to/cc to enumerate individual actor IRIs solely to target specific recipients.

The Context Authority MAY rewrite to/cc to make forwarded deliveries conform to the current Thread Policy, including when:

If the sender's addressing is more permissive than the current Thread Policy, the activity MUST be rejected.

When forwarding an embedded-object activity, rewiring MUST be applied consistently:

"},{"location":"fep/8c13/#content-immutability-normative","title":"Content Immutability (Normative)","text":"

The Context Authority MUST NOT modify any field included in the Author Proof input. Any forwarder mutation MUST be limited to the excluded fields (to, cc, forwardingProof). If any other modification would be required, the Context Authority MUST reject the activity for context routing (it MAY still deliver it locally to directly addressed recipients per legacy behavior).

"},{"location":"fep/8c13/#transport-authentication-for-forwarded-deliveries-normative","title":"Transport Authentication for Forwarded Deliveries (Normative)","text":"

A recipient implementing this FEP MUST authenticate forwarded deliveries via HTTP Signatures (or equivalent) and MUST confirm the request is verifiable using a verification method controlled by the Context Authority's instance actor for the referenced Context Object. This transport-level check is the baseline for accepting forwarded activities: it ensures any addressing updates were performed by the Context Authority without requiring payload-level proofs.

"},{"location":"fep/8c13/#payloadtransport-binding-normative-best-effort","title":"Payload/Transport Binding (Normative, Best-Effort)","text":"

Recipients SHOULD require a request signature profile that binds the request body (e.g. via the Digest header, or Content-Digest/Repr-Digest per RFC 9530) when available. If body binding is unavailable, recipients MAY accept forwarded deliveries but MUST treat addressing updates as transport-trusted rather than payload-verifiable.

"},{"location":"fep/8c13/#optional-forwarding-proof-normative-optional","title":"Optional Forwarding Proof (Normative, Optional)","text":"

Implementations MAY attach and verify a Forwarding Proof that cryptographically binds the forwarded to/cc to the Context Authority, enabling offline verification and reducing reliance on transport-layer body binding. It provides defense-in-depth: even if transport authentication is compromised, the payload-level proof shows the addressing was authorized by the Context Authority.

When a Context Authority attaches a Forwarding Proof, it MUST be computed per Forwarding Proof Canonicalization and placed in the forwardingProof field.

When a recipient receives a Forwarding Proof, it SHOULD verify it and MAY treat success as a stronger signal than transport authentication alone. On failure, it SHOULD fall back to transport authentication and MAY log the failure.

"},{"location":"fep/8c13/#recipient-processing-normative","title":"Recipient Processing (Normative)","text":"

When a server implementing this FEP receives a context-routed activity, it MUST:

  1. Verify transport authentication. Confirm the HTTP request is verifiable using a verification method controlled by the Context Authority's instance actor for the referenced Context Object - the instance actor sharing the origin of the Context Object id (Root Authority Alignment). A forwarded delivery whose signer is not controlled by that origin MUST be rejected. On failure, MUST reject.
  2. Verify the Author Proof. Canonicalize with the Author Proof exclusions, then verify authorProof against the activity actor's verification method. On failure, MUST reject.
  3. Verify the Forwarding Proof if present. Recipients SHOULD verify it chains to the Context Authority's instance-actor key for the resolved Context Object and binds to the Author Proof (via the committed authorProofValue). On success, treat addressing updates as payload-verified; on failure, MAY fall back to transport-authenticated delivery (and MAY log).
  4. Accept. If transport authentication passes and the Author Proof verifies, accept the activity, subject to local policy (blocks, mutes, etc.).

No additional recipient-side authorization checks are required: the Context Authority's transport-authenticated delivery implies the recipient is in the authorized audience. Recipients treat the Context Authority as the policy oracle for delivery authorization, and MUST evaluate visibility/presentation using Effective Addressing.

"},{"location":"fep/8c13/#root-policy-update-propagation-normative","title":"Root Policy Update Propagation (Normative)","text":"

When the root object's addressing (to/cc) changes, the Context Authority MUST publish an Update activity whose object is the full updated root object (same id), and MUST deliver it per ordinary ActivityPub delivery.

Receiving servers implementing this FEP MUST update their stored copy of the root object upon accepting the Update, and MUST treat the updated addressing as the current Thread Policy for the context.

"},{"location":"fep/8c13/#thread-visibility-re-evaluation-normative","title":"Thread Visibility Re-evaluation (Normative)","text":"

Upon learning an updated root object addressing for a context (via Update), a receiving server implementing this FEP SHOULD re-evaluate which local users are authorized under the current Thread Policy, and SHOULD restrict presentation of previously stored context items accordingly. It MAY retain stored objects for legal, abuse, or operational reasons, but MUST treat them as not visible to unauthorized local users. For contexts that have become unauthorized for all local users, servers SHOULD minimize retention duration and metadata exposure, consistent with the deletion guidance in Context Retention and Deletion.

"},{"location":"fep/8c13/#context-discovery-and-backfill-normative","title":"Context Discovery and Backfill (Normative)","text":"

When a server begins hosting an actor that becomes part of the Context Audience after a conversation has started (e.g. Bob follows Alice mid-thread), the server SHOULD attempt to discover and backfill the context, subject to local policy, resource limits, and authorization constraints.

Discovery trigger: Discovery SHOULD be triggered when a server learns it hosts an actor authorized under the current Thread Policy for a context and has learned one or more Context Object URIs for it.

Discovery procedure:

  1. The server SHOULD attempt to dereference the Context Object URI.
  2. If access is permitted, it SHOULD retrieve the Context Object and enumerate its items.
  3. It SHOULD fetch and ingest referenced Context Activities (replies, reactions, updates, deletes, etc.), subject to local retention policy and resource limits.

Fallback (normative): If the Context Authority does not permit remote dereferencing, or dereferencing fails due to authorization or network constraints, the server MUST treat backfill as unavailable and rely solely on delivered activities. Full history convergence is then not guaranteed.

Suggested limits (non-normative): apply paging, caps on pages/items per context, and/or incremental (\"newest first\") backfill to reduce amplification risk and allow partial convergence under load.

"},{"location":"fep/8c13/#example-mid-thread-follow","title":"Example: Mid-Thread Follow","text":"

Alice creates a followers-only thread. Bob follows Alice after 5 Context Activities already exist. Bob's server performs GET https://alice.example/contexts/12345, receives an OrderedCollection with 5 items, and backfills all 5 (including non-reply events such as reactions or edits) to present a complete conversation view.

"},{"location":"fep/8c13/#context-retention-and-deletion-normative","title":"Context Retention and Deletion (Normative)","text":"

If a server reasonably determines (from locally observable state) that it no longer hosts any actor authorized under the current Thread Policy for a Context Object, it SHOULD delete its entire local copy of that context - the Context Object and all Context Activities associated exclusively with it - subject to local policy and legal/operational constraints. Implementations SHOULD apply a grace period to avoid thrash from transient authorization changes.

This prevents indefinite retention of private or restricted conversations by unrelated servers.

"},{"location":"fep/8c13/#example-audience-exhaustion","title":"Example: Audience Exhaustion","text":"

Bob and Carol both unfollow Alice; no local actors remain in Alice's Context Audience. Bob's server SHOULD delete https://alice.example/contexts/12345 and all Context Activities belonging solely to that context. If Bob later re-follows Alice, the context is rediscovered and backfilled per the previous section.

"},{"location":"fep/8c13/#context-payload-resolution","title":"Context Payload Resolution","text":"

When resolving a context URI, the receiving server MUST resolve it to a Context Object and associate Context Activities with that object. The context MUST NOT be resolved to the root Note object, an inReplyTo chain, or a transient/inferred thread identifier. The Context Object is the sole authoritative container for the conversation, ensuring consistent backfill, authorization checks, and lifecycle management across servers.

"},{"location":"fep/8c13/#design-note-non-normative","title":"Design Note (Non-Normative)","text":"

This mechanism intentionally avoids explicit capability discovery. Compliance is inferred per activity from payload semantics: an Effective Context IRI signals routing intent; a valid Author Proof signals eligibility; transport authentication (HTTP Signatures) is the baseline for trusting context-forwarded delivery; and an optional Forwarding Proof provides enhanced payload-level verification. This eliminates downgrade attacks via mixed-compliance networks and allows gradual adoption without coordination.

"},{"location":"fep/8c13/#legacy-interoperability","title":"Legacy Interoperability","text":""},{"location":"fep/8c13/#unknown-context-field-informative","title":"Unknown context Field (Informative)","text":"

Legacy servers ignore the context field, so including it is safe: it causes no extra fetches and no effect on access control.

"},{"location":"fep/8c13/#acceptance-of-legacy-objects","title":"Acceptance of Legacy Objects","text":"

A Context Authority MAY accept legacy objects (objects lacking a valid Author Proof) from legacy peers if the activity is directly addressed to a local actor and local federation policy permits. Acceptance does not imply eligibility for context routing.

"},{"location":"fep/8c13/#forwarding-restrictions-for-legacy-objects","title":"Forwarding Restrictions for Legacy Objects","text":"

Legacy activities:

This ensures recipients relying on integrity guarantees never receive unverifiable content through protected channels.

"},{"location":"fep/8c13/#replies-originating-from-legacy-servers","title":"Replies Originating from Legacy Servers","text":"

When a legacy server replies to a restricted thread, the reply MAY be visible to the directly addressed recipient (e.g. the root author) and MAY be associated with that recipient's local thread view, but MUST NOT be forwarded to the Context Audience. This reflects the limited guarantees of legacy federation while preserving conversation integrity for protected participants.

Example legacy reply (delivered as a Create):

{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"id\": \"https://legacy.example/activities/55555\",\n  \"type\": \"Create\",\n  \"actor\": \"https://legacy.example/u/charlie\",\n  \"to\": [\"https://alice.example/u/alice\"],\n  \"object\": {\n    \"id\": \"https://legacy.example/posts/55555\",\n    \"type\": \"Note\",\n    \"attributedTo\": \"https://legacy.example/u/charlie\",\n    \"inReplyTo\": \"https://alice.example/posts/12345\",\n    \"content\": \"Hello Alice!\",\n    \"to\": [\"https://alice.example/u/alice\"]\n  }\n}\n

This activity lacks an Author Proof, so it cannot be context-forwarded but may be delivered to the directly addressed recipient (Alice).

Sequence: Legacy Reply

sequenceDiagram\n    participant LegacySrv as Legacy Server\n    participant AliceSrv as Context Authority\n    participant CarolSrv as Follower\n\n    LegacySrv->>AliceSrv: Inbox POST Create{Note}\n    AliceSrv->>AliceSrv: Deliver to Alice locally (no Context Object ingest)\n    AliceSrv-x CarolSrv: No forwarding
"},{"location":"fep/8c13/#security-considerations","title":"Security Considerations","text":""},{"location":"fep/8c13/#visibility-changes-and-retroactive-tightening-informative","title":"Visibility Changes and Retroactive Tightening (Informative)","text":"

Updating the root object's addressing lets compliant implementations retroactively restrict presentation of previously stored thread items for local users no longer authorized under the updated Thread Policy. This provides no guarantee of content recall across the federation:

This limitation is inherent to federated systems: the policy update mechanism provides best-effort restriction for compliant implementations, not guaranteed erasure.

"},{"location":"fep/8c13/#references","title":"References","text":""},{"location":"fep/8c13/#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/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.com WITHDRAWN 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.

"},{"location":"fep/8c3f/#example","title":"Example","text":"
{\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://.

"},{"location":"fep/8c3f/#references","title":"References","text":""},{"location":"fep/8c3f/#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/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.com FINAL 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

"},{"location":"fep/8fcf/#partial-follower-collection-digest","title":"Partial follower collection digest","text":"

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

"},{"location":"fep/8fcf/#the-collection-synchronization-http-header","title":"The 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:

"},{"location":"fep/8fcf/#synchronization-procedure","title":"Synchronization procedure","text":""},{"location":"fep/8fcf/#on-the-sender-end","title":"On the sender end","text":"

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.

"},{"location":"fep/8fcf/#on-the-receiving-end","title":"On the receiving end","text":"

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.

"},{"location":"fep/8fcf/#implementations","title":"Implementations","text":""},{"location":"fep/8fcf/#references","title":"References","text":""},{"location":"fep/8fcf/#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/9091/","title":"FEP-9091: Export Actor Service Endpoint","text":"Authors Status Date received Tracking issue Discussions Repository Dmitri Zagidulin @dmitri@social.coop DRAFT 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):

"},{"location":"fep/9091/#example-actor-profile-with-export-service-endpoint","title":"Example Actor Profile with Export Service Endpoint","text":"
{\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:

  1. Fetch the Actor profile object.
  2. Locate the service description object with the expanded type equal to https://w3id.org/fep/9091#Export.
  3. Locate the corresponding serviceEndpoint URL, in that description object.

  4. The client makes an HTTP POST to the URL in the serviceEndpoint to initiate the export operation.

"},{"location":"fep/9091/#example-request-and-response","title":"Example Request and Response","text":"

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":""},{"location":"fep/9091/#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/9098/","title":"FEP-9098: Custom emojis","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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:

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.

"},{"location":"fep/9098/#accessibility","title":"Accessibility","text":"

The short description of a custom emoji can be specified with the alternateName property (the full IRI is http://schema.org/alternateName).

"},{"location":"fep/9098/#using-custom-emojis","title":"Using custom emojis","text":""},{"location":"fep/9098/#microsyntax","title":"Microsyntax","text":"

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.

"},{"location":"fep/9098/#web-client-considerations","title":"Web client considerations","text":"

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:

"},{"location":"fep/9098/#compatibility","title":"Compatibility","text":"

In order to be compatible with as many servers as possible, implementations SHOULD comply with the following additional requirements:

"},{"location":"fep/9098/#implementations","title":"Implementations","text":"

This document is based on implementations of custom emojis in Pleroma, Mastodon, Misskey and Fedibird.

"},{"location":"fep/9098/#references","title":"References","text":""},{"location":"fep/9098/#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/96ff/","title":"FEP-96ff: Explicit signalling of ActivityPub Semantics","text":"Authors Status Date received Tracking issue Discussions Repository Erin Shepherd fep@erinshepherd.net DRAFT 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":""},{"location":"fep/96ff/#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/9967/","title":"FEP-9967: Polls","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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:

The value of this property MUST be an array of objects representing poll options:

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.

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/#counting-voters","title":"Counting voters","text":"

The votersCount property can be used to specify how many people have voted in a poll.

This number will be distinct from the number of votes in case of multiple-choice polls, and can be used to calculate the proportion of participants who voted for a poll option.

"},{"location":"fep/9967/#limits","title":"Limits","text":"

The number of poll options is often limited, but the limit differs between implementations.

Mastodon allows up to 500 options per poll.

"},{"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:

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 is not 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:

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.

"},{"location":"fep/9967/#publishing-results","title":"Publishing results","text":"

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.

"},{"location":"fep/9967/#closing-a-poll","title":"Closing a poll","text":"

A poll is considered to be closed once the date specified by the endTime property has been reached. Implementations MAY add the closed property to explicitly specify the date when the poll was closed.

"},{"location":"fep/9967/#editing-options","title":"Editing options","text":"

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).

"},{"location":"fep/9967/#json-schemas","title":"JSON schemas","text":""},{"location":"fep/9967/#implementations","title":"Implementations","text":"

This document is based on implementations of polls in Mastodon and Pleroma.

Several projects announced support for this FEP after it was published:

"},{"location":"fep/9967/#references","title":"References","text":""},{"location":"fep/9967/#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/9f9f/","title":"FEP-9f9f: Collections","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT informational 2026-04-03 #810 Discussions codeberg"},{"location":"fep/9f9f/#summary","title":"Summary","text":"

This document describes the current best practices for implementing and using ActivityPub collections.

Some parts of it are based on the implementation of collections in GoActivityPub.

"},{"location":"fep/9f9f/#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/9f9f/#what-is-a-collection","title":"What is a collection?","text":""},{"location":"fep/9f9f/#activity-streams-definition","title":"Activity Streams definition","text":"

Activity Streams 2.0: A collection is an object with Collection type or its subtype (e.g. OrderedCollection).

"},{"location":"fep/9f9f/#duck-typing","title":"Duck typing","text":"

FEP-2277: A collection is an object with items, orderedItems, totalItems, partOf, first, last, next, prev or current property.

"},{"location":"fep/9f9f/#ownership","title":"Ownership","text":"

A collection SHOULD have an attributedTo property indicating the actor that created it.

"},{"location":"fep/9f9f/#creating-collections","title":"Creating collections","text":"

Clients can create collections by publishing a Create activity where object is a collection. The server MUST ignore reserved collection properties such as items, totalItems and pagination properties.

Some special collections, such as inbox and outbox are created automatically by a server.

Servers MAY create collections automatically when the object of Create activity contains a property that points to a collection, such as likes, shares or replies. When the specified collection ID is local, the server MUST verify that the ID is valid, and MAY assign a different ID.

Clients MUST NOT attempt to create new collections by embedding non-anonymous collections in objects.

Collection IDs SHOULD NOT contain query parameters.

"},{"location":"fep/9f9f/#updating-collections","title":"Updating collections","text":"

The contents of a collection (its items) are modified either directly by Add, Remove and Move activities, or indirectly by side-effects of other activities. If a collection has an updated property, its value SHOULD be changed whenever the contents are modified.

A client can update other properties of a collection by generating an Update activity. The server MUST NOT overwrite reserved collection properties such as items, totalItems and pagination properties.

Note

Update of a collection is not compatible with client-side signing (FEP-ae97) because partial updates are not supported there.

"},{"location":"fep/9f9f/#ordering","title":"Ordering","text":"

ActivityPub states that an OrderedCollection MUST be presented in reverse chronological order. In practice, however, some ordered collections are presented in forward chronological order, and an erratum was proposed that limits this requirement to collections defined in the ActivityPub specification (inbox, outbox, followers, following, liked, likes and shares).

"},{"location":"fep/9f9f/#filtering","title":"Filtering","text":"

Collections can be filtered.

Filters are specified using query parameters that are appended to a collection ID. A set of guidelines for using these parameters has been proposed in FEP-6606: ActivityPub client to server collections addressing conventions.

"},{"location":"fep/9f9f/#pagination","title":"Pagination","text":"

Pagination of collections is described in Activity Streams 2.0.

Collection page IDs are usually created by appending query parameters to a collection ID:

"},{"location":"fep/9f9f/#access-control","title":"Access control","text":"

Collection items MUST be filtered based on the permissions of an actor that tries to access the collection. If the actor is not authorized, only public items are returned.

A collection itself often doesn't have an audience and is treated as public by default. However, if the audience is specified, regular access control procedures MUST be used.

"},{"location":"fep/9f9f/#addressing","title":"Addressing","text":"

An object can be addressed to a collection. When determining delivery targets and regulating access to an object, the server SHOULD expand collections by replacing them with actors they contain (if any).

"},{"location":"fep/9f9f/#public-collection","title":"Public collection","text":"

https://www.w3.org/ns/activitystreams#Public is a special collection ID that is used for public addressing. It is described in ActivityPub.

"},{"location":"fep/9f9f/#references","title":"References","text":""},{"location":"fep/9f9f/#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/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.uk DRAFT 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:

"},{"location":"fep/9fde/#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/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:

  1. A lot of work for each client
  2. Something that every client needs to do
  3. Easy to get wrong
  4. Doesn't scale across multitudes of different servers
"},{"location":"fep/9fde/#no-standard-way-for-mastodon-servers-to-advertise-that-some-functionality-is-disabled-or-unavailable","title":"No standard way for Mastodon servers to advertise that some functionality is disabled or unavailable","text":"

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.

  1. There is no simple way for clients to know which parts of the Mastodon API the server supports
  2. There is no simple way for clients to know if the server supports additional operations
  3. If Mastodon decides to implement an API that was first introduced in a Mastodon-like server there is no way for clients to detect this, without recompiling the client with new information about what features a given Mastodon server version implements
"},{"location":"fep/9fde/#server-developers-have-too-much-to-do","title":"Server developers have too much to do","text":"

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:

  1. A unique identifer for each operation that a set of servers supports identically
  2. A mechanism for a server to report the operations it supports
"},{"location":"fep/9fde/#a-unique-identifier-for-each-operation","title":"A unique identifier for each operation","text":"

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:

[!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).

"},{"location":"fep/9fde/#each-operation-has-one-or-more-versions","title":"Each operation has one or more versions","text":"

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.

  1. Initial implementation
  2. Support for scheduled_at
  3. Support for poll

There 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.

[!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.

"},{"location":"fep/9fde/#client-discovery-of-supported-operations-and-endpoints","title":"Client discovery of supported operations and endpoints","text":"

Clients 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.

"},{"location":"fep/9fde/#to-deploy-this","title":"To deploy this...","text":""},{"location":"fep/9fde/#server-developers","title":"Server developers","text":"

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:

  1. Determine the operation's version number, following semver backwards-compatible rules
  2. Document the behaviour of the new operation / version
  3. Include the new operation / version in the server's response
"},{"location":"fep/9fde/#client-developers","title":"Client developers","text":"

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.

"},{"location":"fep/9fde/#is-there-a-proof-of-concept","title":"Is there a proof of concept?","text":"

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:

"},{"location":"fep/9fde/#not-in-scope","title":"Not in scope","text":"

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:

  1. It requires every client development team to independently maintain a mapping between server versions and capabilities
  2. It requires client updates whenever a server is released that supports a capability the client already supports on another server

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:

"},{"location":"fep/9fde/#return-an-openapi-definition-for-the-supported-api","title":"Return an OpenAPI definition for the supported API","text":"

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:

So 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:

  1. It suggests that servers might decide to implement the same operation under different URL paths. That is unnecessary extra complexity, and per the previous section, the contract between the client and the server is if the server advertises support for operation X the definition of that operation includes that URL path it is served under.
  2. It's unfriendly to client developers; a client using this information is trying to answer the question \"Does the server support operation X?\", not \"Does API endpoint /a/b/c exist?\". So the operation identifier should be the lookup key for the map, not the endpoint.
"},{"location":"fep/9fde/#open-questions","title":"Open questions","text":"

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.

"},{"location":"fep/9fde/#what-happens-if-an-operations-owner-rebrands-or-disappears","title":"What happens if an operations \"owner\" rebrands or disappears?","text":"

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:

"},{"location":"fep/9fde/#references","title":"References","text":""},{"location":"fep/9fde/#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/a070/","title":"FEP-a070: Ordered properties for plain JSON consumers","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository a a@trwnh.com WITHDRAWN 2023-06-13 2026-04-10 #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.

"},{"location":"fep/a070/#prior-art-in-items-vs-ordereditems","title":"Prior art in 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.

"},{"location":"fep/a070/#an-exploration-of-properties-that-producers-may-wish-to-explicitly-order","title":"An exploration of properties that producers may wish to explicitly order","text":"

(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":""},{"location":"fep/a070/#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/a1d1/","title":"FEP-a1d1: ActivityPub Patch","text":"Authors Status Type Date received Tracking issue Discussions Repository Steve Bate svc-fep@stevebate.net DRAFT implementation 2026-03-19 #796 Discussions codeberg"},{"location":"fep/a1d1/#summary","title":"Summary","text":"

ActivityPub Client-to-Server (C2S) Partial Update relies on shallow top-level replacement and uses JSON null to signal property removal, but in compacted JSON-LD null is elided, so deletions can be lost in transit. This FEP introduces a Patch activity (based on JSON Patch) that cleanly separates patch operations from the target object and supports nested property updates via JSON Pointer (RFC6901).

"},{"location":"fep/a1d1/#motivation","title":"Motivation","text":"

ActivityPub client-to-server updates are currently defined as shallow partial replacements of top-level properties, with a special rule that a property set to JSON null is removed from the stored object. In practice, that removal rule is not compatible with ActivityPub's JSON-LD serialization model.

In compacted JSON-LD, null values are removed and treated as if the property wasn't specified, so a server may never receive the signal that a property is meant to be deleted. This issue has been recorded in the ActivityPub errata, and related discussion in the W3C ActivityPub issue tracker (Issues #396 and #477).

The semantics of the C2S Partial Update is also incorrect. The update is specified in the object property with an id. However, the id is the URI of the target object rather than the update object. In other words, the id is not being used to specify the identity of the Update object, but rather a completely different target object. The Patch operation separates the patch operations from the target object that will be patched.

The C2S Partial Update can only modify top-level properties. The Patch activity can modify nested properties (using JSON Pointer RFC6901 ).

Ideally, the Patch activity will replace partial Update activities in the C2S specification. For complete object replacement, the C2S Update activity can continue to be used in a consistent manner as for the Server-to-Server (S2S) Update.

"},{"location":"fep/a1d1/#patch","title":"Patch","text":"

Based on JSON Patch, a Patch object refers to a PatchOperations object. This object has an operations property containing an ordered list of operations.

Example

{\n    \"@context\": [\n        \"https://w3id.org/fep/a1d1\",\n        \"https://www.w3.org/ns/activitystreams\"\n    ],\n    \"id\": \"https://server.example/patch/1\",\n    \"type\": \"Patch\",\n    \"object\": {\n        \"type\": \"PatchOperations\",\n        \"operations\": [\n            {\n                \"op\": \"add\",\n                \"path\": \"/name\",\n                \"value\": \"Daily post\"\n            },\n            {\n                \"op\": \"remove\",\n                \"path\": \"/summary\",\n            },\n            {\n                \"value\": \"Some new content...\",\n                \"path\": \"/content\",\n                \"op\": \"replace\"\n            },\n            {\n                \"value\": \"Du nouveau contenu...\",\n                \"path\": \"/contentMap/fr\",\n                \"op\": \"replace\"\n            },\n        ]\n    },\n    \"target\": \"http://object-to-update\"\n}\n

The operations are standard JSON Patch operations so they will not be covered in detail in this FEP. The operations include:

RFC6902 contains numerous examples that describe the semantics of these operations. This FEP defines a JSON-LD context for the Patch activity and the JSON Patch language.

"},{"location":"fep/a1d1/#side-effects","title":"Side-Effects","text":"

When a Patch activity is posted to the outbox, the server MUST apply authorized operations to the target object. After the update is complete, the server SHOULD send an Update notification (with the full, updated object) notification, on behalf of the Patch actor, to any specified recipients.

"},{"location":"fep/a1d1/#json-ld-context","title":"JSON-LD Context","text":"
{\n    \"@context\": [\n        {\n            \"@version\": 1.1,\n            \"fep-a1d1\": \"https://w3id.org/fep/a1d1#\",\n            \"Patch\": \"fep-a1d1:Patch\",\n            \"PatchOperations\": {\n                \"@id\": \"fep-a1d1:PatchOperations\",\n                \"@context\": {\n                    \"operations\": {\n                        \"@id\": \"fep-a1d1:operations\",\n                        \"@container\": \"@list\",\n                        \"@context\": {\n                            \"op\": \"fep-a1d1:op\",\n                            \"path\": \"fep-a1d1:path\",\n                            \"value\": { \n                                \"@id\": \"fep-a1d1:value\", \n                                \"@type\": \"@json\"\n                            },\n                            \"from\": \"fep-a1d1:from\",\n                            \"add\": \"fep-a1d1:add\",\n                            \"remove\": \"fep-a1d1:remove\",\n                            \"replace\": \"fep-a1d1:replace\",\n                            \"move\": \"fep-a1d1:move\",\n                            \"copy\": \"fep-a1d1:copy\",\n                            \"test\": \"fep-a1d1:test\"\n                        }\n                    }\n                }\n            }\n        }\n    ]\n}\n

NOTE: The fep-a1d1:value term is defined as a JSON-LD @json type. For JSON consumers, this will not make a difference but it tells JSON-LD consumers that the content is a serialized JSON string (which may itself be JSON-LD). Linked Data applications will typically need to parse this string to retrieve the original JSON content.

"},{"location":"fep/a1d1/#implementations","title":"Implementations","text":""},{"location":"fep/a1d1/#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/a427/","title":"FEP-a427: Server Domain Migration","text":"Authors Status Date received Tracking issue Discussions Repository Dmitry Skavish skavish@gmail.com DRAFT 2026-02-25 #767 Discussions codeberg"},{"location":"fep/a427/#summary","title":"Summary","text":"

This FEP defines a best-effort protocol for migrating an entire ActivityPub server from one domain to another when the operator controls both domains and can keep the old domain online.

Example scenario used throughout this document:

This proposal introduces:

This FEP is explicitly best-effort. It does not guarantee preservation of all follows across all peers.

"},{"location":"fep/a427/#scope","title":"Scope","text":"

This FEP addresses server domain renaming: one source server migrating to one destination server with a one-to-one mapping of all identifiers. It does not cover merging multiple source servers into a single destination.

"},{"location":"fep/a427/#motivation","title":"Motivation","text":""},{"location":"fep/a427/#the-core-problem","title":"The Core Problem","text":"

In practice, ActivityPub identifiers are domain-bound: https://sunset.social/users/alice

Remote servers treat that URL as Alice's identity.

If sunset.social changes its domain to dawn.network, then: https://dawn.network/users/alice is treated as a completely different actor unless peers are informed and update their local state.

Current practice requires:

For large instances, this is inefficient and unreliable.

"},{"location":"fep/a427/#why-not-just-send-old-domain-new-domain","title":"Why Not Just Send \"Old Domain \u2192 New Domain\"?","text":"

It might seem sufficient to send:

ServerMove { from: sunset.social, to: dawn.network }\n

That is insufficient for three independent reasons.

"},{"location":"fep/a427/#1-deterministic-rewriting-why-mapping-exists","title":"1. Deterministic Rewriting (Why Mapping Exists)","text":"

Remote servers need a deterministic algorithm for rewriting identifiers.

If actor paths are preserved:

https://sunset.social/users/alice\n\u2192 https://dawn.network/users/alice\n

then a rule-based mapping works: replace origin, preserve path.

But if:

then simple domain substitution fails.

Mapping exists to define precisely how old identifiers derive new identifiers.

Without mapping:

"},{"location":"fep/a427/#2-identity-hijacking-prevention-why-acceptance-exists","title":"2. Identity Hijacking Prevention (Why Acceptance Exists)","text":"

If only one signed message were required, an attacker could send: ServerMove sunset.social \u2192 evil.example

If peers trusted that blindly, they would rewrite all identifiers and effectively transfer followers to the attacker.

A signature proves who sent the message, not that they control both domains.

Therefore migration requires:

Both domains must independently assert the migration.

Only when both sides agree may peers apply changes.

"},{"location":"fep/a427/#3-durability-and-idempotency-why-manifest-exists","title":"3. Durability and Idempotency (Why Manifest Exists)","text":"

A single notification message is:

Peers require a durable object that:

That durable object is the ServerMigration manifest.

"},{"location":"fep/a427/#terminology","title":"Terminology","text":""},{"location":"fep/a427/#servermigration-manifest","title":"ServerMigration (Manifest)","text":"

A persistent ActivityStreams object describing:

It is the canonical description of the migration.

Think of it as: \"The official migration document.\"

"},{"location":"fep/a427/#servermigrationacceptance","title":"ServerMigrationAcceptance","text":"

A persistent object hosted on the destination server confirming:

Think of it as: \"We agree to receive these identities.\"

"},{"location":"fep/a427/#servermove","title":"ServerMove","text":"

A lightweight ActivityPub activity sent to peers that says:

\"Please fetch and apply this migration manifest.\"

It does not contain full migration details.

"},{"location":"fep/a427/#mapping","title":"Mapping","text":"

A deterministic rule that converts old URIs to new URIs. Given the same mapping rules and the same input URI, every implementation MUST produce the same output URI.

Three types exist:

  1. Origin-based mapping Replace origin (scheme + host + port), preserve path.

  2. Prefix-based mapping Replace URI prefixes, preserving remaining path segments.

  3. Regex-based mapping Rewrite URIs using regular expression pattern matching (RE2 semantics).

All mapping rules MUST be reversible: for every old URI that maps to a new URI, it MUST be possible to recover the original old URI from the new URI using the same mapping definition. For OriginReplace and PrefixReplace, this is satisfied by swapping from/to values. For RegexReplace, explicit reverse rules are required.

Mapping prevents guesswork and avoids publishing global user directories.

"},{"location":"fep/a427/#uri-rewriting-scope","title":"URI Rewriting Scope","text":"

Mapping rules are used to compute new URIs for actor identifiers originating from the source server. Peers establish aliases between old and new actor URIs but MUST NOT rewrite non-actor object IDs in place (see Applying the Migration Locally).

Non-actor URIs (object IDs, collection URLs, activity IDs, media URLs) are resolved via HTTP redirects served by the source domain, not by rewriting stored values.

"},{"location":"fep/a427/#conformance","title":"Conformance","text":"

The key words MUST, SHOULD, MAY, etc. are to be interpreted as described in RFC 2119.

"},{"location":"fep/a427/#uri-normalization","title":"URI Normalization","text":"

This specification uses origin (scheme + host + port) as defined in RFC 6454 to identify servers. An origin is the tuple (scheme, host, port).

When comparing or matching URIs, implementations MUST apply the following normalization:

  1. Scheme: lowercase (e.g., HTTPS \u2192 https).
  2. Host: lowercase, converted to ASCII via punycode for internationalized domain names (IDN) per RFC 5891.
  3. Port: the default port for the scheme MUST be omitted. For https, port 443 is default and MUST NOT appear explicitly. https://example.com:443/ and https://example.com/ are the same origin.
  4. Path: preserved exactly as-is. No normalization of path segments, percent-encoding, or trailing slashes (except that the empty path is equivalent to /).

Two URIs are same-origin if and only if their normalized origins are identical.

The fromOrigin and toOrigin values in OriginReplace mappings MUST be normalized origins (e.g., https://sunset.social, not https://Sunset.Social:443).

"},{"location":"fep/a427/#high-level-flow","title":"High-Level Flow","text":"
sequenceDiagram\nautonumber\nparticipant sunset as sunset.social\nparticipant sunsetActor as sunset.social server actor\nparticipant dawn as dawn.network\nparticipant dawnActor as dawn.network server actor\nparticipant Forest as forest.instance\nparticipant ForestActor as forest.instance server actor\n\nsunset->>sunsetActor: Create ServerMigration manifest (M)\ndawn->>dawnActor: Create ServerMigrationAcceptance (A)\n\nsunsetActor->>Forest: Discover server actor via WebFinger (FEP-d556)\nForest-->>sunsetActor: Returns ForestActor\n\nsunsetActor->>ForestActor: POST ServerMove {object=M}\nForestActor->>sunset: GET M\nForestActor->>dawn: GET A\nForestActor->>ForestActor: Verify proofs, actor match, cross-references\nForestActor->>ForestActor: Establish actor aliases (old \u2192 new URIs)\nForestActor->>dawn: Fetch new actor objects\nForestActor->>ForestActor: Verify alsoKnownAs linkage\nForestActor->>ForestActor: Update delivery endpoints, keys, follows\n\nloop Poll until completed or rolledBack\nForestActor->>sunset: GET M (check state)\nend
"},{"location":"fep/a427/#specification","title":"Specification","text":""},{"location":"fep/a427/#1-server-actor-discovery","title":"1. Server Actor Discovery","text":"

Server actor discovery MUST follow FEP-d556: Server Actor Discovery, which defines a \"server\" as an origin (scheme + host + port) and specifies how to discover the server-level actor via WebFinger.

The source server, destination server, and all peers MUST expose a server-level actor discoverable via FEP-d556.

Peers MUST discover and use the server actor inbox for ServerMove delivery.

"},{"location":"fep/a427/#1a-webfinger-behavior-during-migration","title":"1a. WebFinger Behavior During Migration","text":"

During an active or completed migration, the source server's WebFinger responses MUST reflect the migration state so that peers performing fresh lookups discover the canonical identities.

For actor lookups (e.g., ?resource=acct:alice@sunset.social):

During the active and completed phases, the source server MUST return a WebFinger response that includes an aliases array containing the new actor URI:

{\n  \"subject\": \"acct:alice@sunset.social\",\n  \"aliases\": [\n    \"https://sunset.social/users/alice\",\n    \"https://dawn.network/users/alice\"\n  ],\n  \"links\": [\n    {\n      \"rel\": \"self\",\n      \"type\": \"application/activity+json\",\n      \"href\": \"https://dawn.network/users/alice\"\n    }\n  ]\n}\n

The rel=\"self\" link MUST point to the new canonical actor URI on the destination server. This ensures that peers performing a fresh WebFinger lookup are directed to the new identity even if they have not yet processed the ServerMove.

For server actor lookups (e.g., ?resource=https://sunset.social/):

The source server MUST continue to return its own server actor during the active phase (the server actor is needed to verify the manifest proof). After the migration is completed, the server actor WebFinger response SHOULD include an alias pointing to the new server actor.

"},{"location":"fep/a427/#2-servermigration-object","title":"2. ServerMigration Object","text":"

A ServerMigration object MUST include:

Property Type Description @context Array MUST include \"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/a427\", and \"https://w3id.org/security/data-integrity/v1\" id URI Stable, dereferenceable URI hosted on the source server type String \"ServerMigration\" source URI Server actor ID of the source server target URI Server actor ID of the destination server mapping Object Mapping rules (see Mapping Rules) state String One of: active, completed, rolledBack published xsd:dateTime When the manifest was first published updated xsd:dateTime When the manifest state last changed (MUST be present when state is not active) acceptance URI Dereferenceable URL of the ServerMigrationAcceptance on the destination server proof Object FEP-8b32 Object Integrity Proof (see Cryptographic Proofs)"},{"location":"fep/a427/#pre-generating-ids","title":"Pre-generating IDs","text":"

Because the ServerMigration references its ServerMigrationAcceptance (via acceptance) and the acceptance references the manifest (via migration), both IDs MUST be determined before either object is published. Since both objects are controlled by the same operator, the recommended approach is:

  1. Generate both IDs deterministically (e.g., based on a shared migration identifier such as a date or UUID).
  2. Publish the ServerMigration manifest on the source server.
  3. Publish the ServerMigrationAcceptance on the destination server.

Example ID scheme:

ServerMigration:  https://sunset.social/.well-known/server-migration/2026-02-23\nAcceptance:       https://dawn.network/.well-known/server-migration-acceptance/2026-02-23\n
"},{"location":"fep/a427/#full-example","title":"Full Example","text":"
{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/a427\",\n    \"https://w3id.org/security/data-integrity/v1\"\n  ],\n  \"id\": \"https://sunset.social/.well-known/server-migration/2026-02-23\",\n  \"type\": \"ServerMigration\",\n  \"source\": \"https://sunset.social/actor\",\n  \"target\": \"https://dawn.network/actor\",\n  \"mapping\": {\n    \"type\": \"OriginReplace\",\n    \"fromOrigin\": \"https://sunset.social\",\n    \"toOrigin\": \"https://dawn.network\"\n  },\n  \"state\": \"active\",\n  \"published\": \"2026-02-23T00:00:00Z\",\n  \"acceptance\": \"https://dawn.network/.well-known/server-migration-acceptance/2026-02-23\",\n  \"proof\": {\n    \"type\": \"DataIntegrityProof\",\n    \"cryptosuite\": \"eddsa-jcs-2022\",\n    \"verificationMethod\": \"https://sunset.social/actor#ed25519-key\",\n    \"proofPurpose\": \"assertionMethod\",\n    \"proofValue\": \"z...\"\n  }\n}\n
"},{"location":"fep/a427/#3-servermigrationacceptance-object","title":"3. ServerMigrationAcceptance Object","text":"

Hosted on the destination server (dawn.network), MUST include:

Property Type Description @context Array MUST include \"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/a427\", and \"https://w3id.org/security/data-integrity/v1\" id URI Stable, dereferenceable URI hosted on the destination server type String \"ServerMigrationAcceptance\" migration URI Reference to the ServerMigration manifest source URI Server actor ID of the source server (MUST match manifest source) target URI Server actor ID of the destination server (MUST match manifest target) proof Object FEP-8b32 Object Integrity Proof signed by the destination server actor"},{"location":"fep/a427/#full-example_1","title":"Full Example","text":"
{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/a427\",\n    \"https://w3id.org/security/data-integrity/v1\"\n  ],\n  \"id\": \"https://dawn.network/.well-known/server-migration-acceptance/2026-02-23\",\n  \"type\": \"ServerMigrationAcceptance\",\n  \"migration\": \"https://sunset.social/.well-known/server-migration/2026-02-23\",\n  \"source\": \"https://sunset.social/actor\",\n  \"target\": \"https://dawn.network/actor\",\n  \"proof\": {\n    \"type\": \"DataIntegrityProof\",\n    \"cryptosuite\": \"eddsa-jcs-2022\",\n    \"verificationMethod\": \"https://dawn.network/actor#ed25519-key\",\n    \"proofPurpose\": \"assertionMethod\",\n    \"proofValue\": \"z...\"\n  }\n}\n
"},{"location":"fep/a427/#4-servermove-activity","title":"4. ServerMove Activity","text":"

A lightweight activity delivered to peers to trigger migration processing.

Property Type Description @context Array MUST include \"https://www.w3.org/ns/activitystreams\" and \"https://w3id.org/fep/a427\" type String \"ServerMove\" actor URI Server actor of the source server object URI URL of the ServerMigration manifest

Example:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/a427\"\n  ],\n  \"type\": \"ServerMove\",\n  \"actor\": \"https://sunset.social/actor\",\n  \"object\": \"https://sunset.social/.well-known/server-migration/2026-02-23\"\n}\n

This MUST be delivered via signed HTTP POST to the peer's server actor inbox.

"},{"location":"fep/a427/#5-cryptographic-proofs","title":"5. Cryptographic Proofs","text":"

Both ServerMigration and ServerMigrationAcceptance objects MUST include a proof property conforming to FEP-8b32: Object Integrity Proofs.

FEP-8b32 proofs are based on the W3C Data Integrity specification and attach a cryptographic signature directly to the object. This ensures authenticity can be verified regardless of how the object was obtained (direct fetch, cache, forwarding).

"},{"location":"fep/a427/#requirements","title":"Requirements","text":""},{"location":"fep/a427/#verification-method-resolution","title":"Verification Method Resolution","text":"

Per FEP-8b32, the verificationMethod in each proof references a key in a controlled identifier document (typically the actor object). Peers MUST:

  1. Fetch the server actor object identified by source (or target for the acceptance).
  2. Verify that the verificationMethod URI resolves to a key listed in the actor's assertionMethod property (see also FEP-521a).
  3. Verify that the verificationMethod is same-origin with the signed document. Cross-origin verification methods MUST be rejected unless the implementation explicitly supports decentralized identifier (DID) based methods.
"},{"location":"fep/a427/#proof-stability","title":"Proof Stability","text":"

The proof property covers the object at a point in time. When the state field changes (e.g., from active to completed), the source server MUST re-sign the manifest with an updated proof. Peers MUST re-verify the proof each time they fetch the manifest during polling.

The ServerMove activity itself is authenticated via HTTP Signatures on the POST request, as is standard for ActivityPub server-to-server delivery.

"},{"location":"fep/a427/#6-fetch-safety","title":"6. Fetch Safety","text":"

Processing a ServerMove requires fetching URLs supplied by a remote party. Without constraints, this creates SSRF (Server-Side Request Forgery) and resource-exhaustion vectors. Implementations MUST enforce the following:

"},{"location":"fep/a427/#origin-constraints","title":"Origin Constraints","text":""},{"location":"fep/a427/#resource-limits","title":"Resource Limits","text":""},{"location":"fep/a427/#receiving-rules","title":"Receiving Rules","text":"

Upon receiving ServerMove, a peer:

  1. MUST verify the HTTP Signature on the delivery request.
  2. MUST fetch the ServerMigration manifest from the URL in object.
  3. MUST verify that ServerMove.actor equals ServerMigration.source. If they differ, the peer MUST reject the migration.
  4. MUST verify the manifest's FEP-8b32 proof by resolving the verificationMethod from the source server actor's assertionMethod (see Cryptographic Proofs).
  5. MUST fetch the ServerMigrationAcceptance from the URL in acceptance.
  6. MUST verify the acceptance's FEP-8b32 proof by resolving the verificationMethod from the destination server actor's assertionMethod.
  7. MUST verify that acceptance.migration references the manifest's id.
  8. MUST verify that source and target are consistent across both objects.
  9. MUST apply the migration locally only if all verification succeeds (see Applying the Migration Locally).
  10. MUST begin polling the manifest for lifecycle state changes (see Polling).
"},{"location":"fep/a427/#mapping-resolution","title":"Mapping Resolution","text":"

Mapping rules define how to derive a new URI from an old URI. Mapping is applied only to actor URIs (see Applying the Migration Locally).

flowchart TD\nA[Old actor URI<br/>e.g. https://sunset.social/users/alice]\n  --> B{Does URI origin match<br/>source origin?}\n\nB -->|No| G[Leave unchanged]\nB -->|Yes| C{Mapping type?}\n\nC -->|OriginReplace| D[Replace origin<br/>https://sunset.social \u2192 https://dawn.network]\nD --> E[New URI<br/>https://dawn.network/users/alice]\n\nC -->|PrefixReplace| F[Try rules in order<br/>first match wins]\nF -->|Match found| E\nF -->|No match| H[Leave unchanged<br/>Log warning]\n\nC -->|RegexReplace| I[Try regex rules in order<br/>first match wins]\nI -->|Match found| J{Result is valid<br/>HTTPS URI on target?}\nJ -->|Yes| E\nJ -->|No| H\nI -->|No match| H

When a PrefixReplace or RegexReplace mapping contains multiple rules, peers MUST evaluate them in the order they appear in the rules array. The first matching rule wins. If no rule matches a URI whose origin matches the source server, the URI is left unchanged and the peer SHOULD log a warning for operator review.

Peers MUST treat migration as idempotent per manifest id.

"},{"location":"fep/a427/#applying-the-migration-locally","title":"Applying the Migration Locally","text":"

Peers MUST NOT interpret migration as a naive string-replace across all stored data. A peer's local state includes delivery endpoints, public keys, follow relationships with internal identifiers, moderation decisions, cached objects with provenance information, and historical activities that reference old actor IDs. Blindly rewriting all occurrences would break signature verification on historical objects, corrupt Undo/Accept semantics (which reference the ID at the time the original activity was created), and bypass blocklists and moderation state.

Instead, peers MUST apply the following model.

"},{"location":"fep/a427/#step-1-establish-actor-aliases","title":"Step 1: Establish Actor Aliases","text":"

For each actor whose URI origin matches the source server, compute the new URI using the mapping rules. Register the old URI as an alias of the new URI:

"},{"location":"fep/a427/#step-2-fetch-and-verify-new-actors","title":"Step 2: Fetch and Verify New Actors","text":"

For each mapped actor that the peer has a local relationship with (follows, followers, cached profiles), the peer MUST:

  1. Fetch the new actor object from the new URI.
  2. Verify linkage: the new actor SHOULD include an alsoKnownAs property (as an array) containing the old actor URI. If present, this confirms the migration from the actor's perspective.
  3. Update locally-stored delivery metadata: inbox, sharedInbox, endpoints, and publicKey MUST be replaced with the values from the fetched new actor object.

If the new actor cannot be fetched (network error, 404), the peer SHOULD retain the alias but MUST NOT update delivery metadata. The peer SHOULD retry on subsequent delivery attempts.

"},{"location":"fep/a427/#step-3-carry-over-moderation-state","title":"Step 3: Carry Over Moderation State","text":"

Block lists, mutes, silences, and other moderation decisions associated with the old actor URI MUST be carried over to the new canonical URI. The old alias MUST also continue to match moderation rules so that references in historical content remain covered.

"},{"location":"fep/a427/#step-4-non-actor-uris-objects-collections-media","title":"Step 4: Non-Actor URIs (Objects, Collections, Media)","text":"

Peers MUST NOT rewrite non-actor object IDs (notes, articles, activity IDs, collection URLs, media URLs) in place. Object IDs are treated as identifiers in the ActivityPub data model, not merely as addresses. Rewriting them in place would create:

Instead, non-actor URIs from the source server continue to be stored under their original IDs. When the old domain serves HTTP 301 redirects (see Old Domain Lifetime), peers that need to re-fetch these objects will be transparently redirected to the new location.

Peers MAY maintain an internal equivalence index (old URI \u2192 new URI) for non-actor URIs to optimize lookups, but the stored canonical ID MUST remain the original.

"},{"location":"fep/a427/#step-5-transfer-follow-relationships","title":"Step 5: Transfer Follow Relationships","text":"

All existing follow relationships involving actors from the source server MUST be automatically transferred to the new canonical URIs:

This is an automatic, server-side operation. Individual follow approval policies on the receiving server are not re-triggered \u2014 the follow relationship is preserved, not re-established.

"},{"location":"fep/a427/#source-server-requirements","title":"Source Server Requirements","text":"

To support the fetch-and-verify step, the source server (or destination server, since they are operated by the same party) SHOULD ensure that each migrated actor on the new domain includes an alsoKnownAs property referencing the old URI as an array:

{\n  \"id\": \"https://dawn.network/users/alice\",\n  \"type\": \"Person\",\n  \"alsoKnownAs\": [\"https://sunset.social/users/alice\"],\n  \"inbox\": \"https://dawn.network/users/alice/inbox\",\n  \"publicKey\": { \"...\" }\n}\n

This provides per-actor verification that complements the server-level manifest proofs.

"},{"location":"fep/a427/#actor-opt-out","title":"Actor Opt-Out","text":"

Individual actors cannot opt out of a server migration. Domain migration is an operational decision made by the server operator and applies to all actors hosted on that server. This is analogous to other server-wide operational changes (TLS certificate rotation, IP address changes) that do not require per-actor consent.

Actors who disagree with the migration may independently move to a different server using per-actor Move after the migration completes.

"},{"location":"fep/a427/#polling","title":"Polling","text":"

After applying a migration, peers MUST periodically poll the ServerMigration manifest to track lifecycle state changes.

"},{"location":"fep/a427/#polling-strategy","title":"Polling Strategy","text":"

Peers SHOULD use the following polling schedule:

Time Since Migration Applied Poll Interval First 24 hours Every 1 hour Days 2\u20137 Every 6 hours Days 8\u201330 Every 24 hours After 30 days Every 7 days

Peers MUST continue polling until the manifest state changes to completed or rolledBack.

"},{"location":"fep/a427/#on-state-change-to-completed","title":"On State Change to completed","text":"

The peer marks the migration as finalized. No further polling is needed for this manifest.

"},{"location":"fep/a427/#on-state-change-to-rolledback","title":"On State Change to rolledBack","text":"

The peer MUST reverse the migration by restoring old actor aliases as canonical and demoting the new URIs. Since actors were migrated using the alias model (Step 1), the old URI is still stored. No mapping rule inversion is needed for actor URIs.

The peer MUST re-fetch actor objects from the original (source) server to restore delivery metadata (inbox, keys).

Since non-actor URIs were never rewritten (Step 4), no reversal is needed for object IDs, collection URLs, or media URLs.

However, if an individual actor performed a per-actor Move activity during the active phase (i.e., the actor independently moved to a different server while the server-wide migration was in progress), that per-actor Move takes precedence. The server-wide rollback MUST NOT override an actor's intentional individual move. Peers SHOULD check whether each actor has a more recent per-actor Move on record before reversing the alias.

After processing a rollback, the peer MUST mark this migration ID as permanently invalid. If a ServerMove referencing this same manifest id is received again in the future, the peer MUST reject it.

"},{"location":"fep/a427/#on-fetch-failure","title":"On Fetch Failure","text":"

If a poll request fails (network error, HTTP 5xx), the peer SHOULD retry using exponential backoff starting at 1 hour, capped at 24 hours.

If the manifest becomes permanently unreachable (HTTP 404 or 410 for 7 consecutive days), the peer SHOULD stop polling. However, the peer MUST NOT infer completed from unreachability alone. The migration remains in whatever state was last observed and verified. The peer SHOULD log a warning so operators can investigate.

"},{"location":"fep/a427/#lifecycle","title":"Lifecycle","text":"
stateDiagram-v2\n[*] --> Active\nActive --> Completed\nActive --> RolledBack

RolledBack and Completed are terminal states. A rolled-back migration cannot be reactivated. If the operator wishes to attempt the migration again, a new ServerMigration manifest with a new id MUST be created.

"},{"location":"fep/a427/#grace-period","title":"Grace Period","text":"

Once a migration reaches the completed state, it MUST NOT be changed to rolledBack. Rollback is only available from the active state. This prevents long-term state instability where peers that already finalized the migration would need to reverse months-old changes.

Operators who need to reverse a completed migration MUST treat it as a new migration in the opposite direction (from dawn.network back to sunset.social), using a new manifest.

"},{"location":"fep/a427/#per-actor-move-precedence","title":"Per-Actor Move Precedence","text":"

Per-actor Move activities always take precedence over server-wide migration state. If an individual actor performed a Move to a third-party server while the server-wide migration was active, that actor's individual move MUST NOT be overridden by a subsequent server-wide rollback. Peers SHOULD track per-actor moves independently of server-wide migrations.

Peers MUST:

"},{"location":"fep/a427/#migration-history","title":"Migration History","text":"

Servers MUST retain all ServerMigration manifests indefinitely, regardless of their terminal state. This allows peers that were offline during the migration window to discover and process migrations after the fact.

Both the source and destination servers SHOULD serve the manifest at its original URL. The destination server SHOULD additionally serve a copy of the manifest so that peers can discover it even if the source domain eventually becomes unavailable.

"},{"location":"fep/a427/#conflicting-migrations","title":"Conflicting Migrations","text":"

A source server MUST NOT have more than one migration in the active state at any time. If a peer receives a ServerMove for a source server that already has an active migration with a different manifest id, the peer MUST reject the new migration and SHOULD log a warning.

Once a migration reaches a terminal state (completed or rolledBack), a new migration from the same source server is permitted.

"},{"location":"fep/a427/#chained-migrations","title":"Chained Migrations","text":"

If a server migrates from A to B (completed), and later from B to C, peers that processed the first migration will encounter the second migration naturally. Peers that missed the first migration and later discover A\u2192B (completed) and B\u2192C (active or completed) SHOULD apply both in sequence.

"},{"location":"fep/a427/#security-considerations","title":"Security Considerations","text":"

Migration requires two-party agreement.

sequenceDiagram\nparticipant Evil as evil.example\nparticipant Forest as forest.instance\n\nEvil->>Forest: ServerMove sunset.social->evil.example\nForest->>sunset.social: GET Manifest\nForest->>Forest: Verify proof \u2014 signed by sunset.social? No.\nForest->>Forest: Reject migration

A migration is valid only if:

Without both proofs, identity hijacking is prevented.

"},{"location":"fep/a427/#ssrf-and-resource-exhaustion","title":"SSRF and Resource Exhaustion","text":"

Because peers fetch URLs supplied by remote parties, implementations MUST enforce the origin constraints and resource limits defined in Fetch Safety. In particular:

"},{"location":"fep/a427/#domain-takeover-after-migration","title":"Domain Takeover After Migration","text":"

If the source domain (sunset.social) expires after migration and is re-registered by a different party, the new registrant could stand up a server actor with new keys and attempt to publish a fraudulent ServerMigration manifest.

To mitigate this risk:

"},{"location":"fep/a427/#privacy-considerations","title":"Privacy Considerations","text":""},{"location":"fep/a427/#fallback","title":"Fallback","text":"

If forest.instance does not support this FEP:

"},{"location":"fep/a427/#old-domain-lifetime","title":"Old Domain Lifetime","text":"

This FEP does not mandate a specific duration for maintaining the old domain. However, the following recommendations apply:

Operators should be aware that some peers may be offline for extended periods. The longer the old domain remains operational, the more peers will successfully process the migration.

"},{"location":"fep/a427/#old-uri-resolution-tombstones-vs-redirects","title":"Old URI Resolution (Tombstones vs Redirects)","text":"

When the old domain is still operational, old URIs SHOULD be handled as HTTP 301 (Moved Permanently) redirects to the corresponding new URIs. This is preferred over returning Tombstone objects because:

Tombstone objects SHOULD NOT be used for migrated resources because they signal deletion, which may cause peers to remove cached content rather than update references.

The source server SHOULD return the manifest itself (not a redirect) at its .well-known URL, so that peers can always fetch it for verification.

"},{"location":"fep/a427/#deployment-phases","title":"Deployment Phases","text":"
gantt\ntitle sunset.social \u2192 dawn.network Migration\ndateFormat  YYYY-MM-DD\n\nsection Prepare\nStand up dawn.network :a1, 2026-02-23, 3d\nGenerate manifest and acceptance IDs :a1b, after a1, 1d\nPublish manifests on both servers     :a2, after a1b, 1d\n\nsection Notify\nDiscover peers           :b1, after a2, 3d\nSend ServerMove waves    :b2, after b1, 7d\n\nsection Fallback\nPer-actor Move           :c1, after b1, 14d\nMaintain old domain      :c2, 2026-02-23, 90d\n\nsection Complete\nMark completed           :d1, after b2, 1d
"},{"location":"fep/a427/#mapping-rules","title":"Mapping Rules","text":"

Scenario used in examples:

"},{"location":"fep/a427/#1-simple-origin-based-mapping","title":"1. Simple Origin-Based Mapping","text":""},{"location":"fep/a427/#use-case","title":"Use Case","text":"

URL structure remains identical. Only the origin (scheme + host) changes.

"},{"location":"fep/a427/#rule-definition-conceptual","title":"Rule Definition (Conceptual)","text":"
If URI origin equals \"https://sunset.social\"\nReplace origin with \"https://dawn.network\"\nPreserve path\n
"},{"location":"fep/a427/#example-transformations","title":"Example Transformations","text":"
https://sunset.social/users/alice\n\u2192 https://dawn.network/users/alice\n\nhttps://sunset.social/@bob\n\u2192 https://dawn.network/@bob\n\nhttps://sunset.social/groups/astronomy\n\u2192 https://dawn.network/groups/astronomy\n\nhttps://sunset.social/notes/12345\n\u2192 https://dawn.network/notes/12345\n
"},{"location":"fep/a427/#manifest-example","title":"Manifest Example","text":"
{\n  \"type\": \"ServerMigration\",\n  \"mapping\": {\n    \"type\": \"OriginReplace\",\n    \"fromOrigin\": \"https://sunset.social\",\n    \"toOrigin\": \"https://dawn.network\"\n  }\n}\n
"},{"location":"fep/a427/#2-prefix-based-mapping-path-changes","title":"2. Prefix-Based Mapping (Path Changes)","text":""},{"location":"fep/a427/#use-case_1","title":"Use Case","text":"

Old server used /users/ New server uses /profile/

"},{"location":"fep/a427/#transformations","title":"Transformations","text":"
https://sunset.social/users/alice\n\u2192 https://dawn.network/profile/alice\n
"},{"location":"fep/a427/#rule-definition","title":"Rule Definition","text":"
Match prefix: https://sunset.social/users/\nRewrite to: https://dawn.network/profile/\nAppend remaining path segment\n
"},{"location":"fep/a427/#manifest-example_1","title":"Manifest Example","text":"
{\n  \"mapping\": {\n    \"type\": \"PrefixReplace\",\n    \"rules\": [\n      {\n        \"fromPrefix\": \"https://sunset.social/users/\",\n        \"toPrefix\": \"https://dawn.network/profile/\"\n      }\n    ]\n  }\n}\n

Deterministic. Still does not expose actor lists.

"},{"location":"fep/a427/#3-multiple-actor-types-users-groups","title":"3. Multiple Actor Types (Users + Groups)","text":""},{"location":"fep/a427/#use-case_2","title":"Use Case","text":"

Users and groups have different routes.

https://sunset.social/users/alice\n\u2192 https://dawn.network/u/alice\n\nhttps://sunset.social/groups/astronomy\n\u2192 https://dawn.network/g/astronomy\n
"},{"location":"fep/a427/#manifest-example_2","title":"Manifest Example","text":"
{\n  \"mapping\": {\n    \"type\": \"PrefixReplace\",\n    \"rules\": [\n      {\n        \"fromPrefix\": \"https://sunset.social/users/\",\n        \"toPrefix\": \"https://dawn.network/u/\"\n      },\n      {\n        \"fromPrefix\": \"https://sunset.social/groups/\",\n        \"toPrefix\": \"https://dawn.network/g/\"\n      }\n    ]\n  }\n}\n

Rules are evaluated in order. The first matching rule is applied.

"},{"location":"fep/a427/#4-catch-all-with-prefix-rules","title":"4. Catch-All with Prefix Rules","text":"

When using PrefixReplace, it is RECOMMENDED to include a catch-all rule as the last entry to handle URIs that don't match specific prefix patterns:

{\n  \"mapping\": {\n    \"type\": \"PrefixReplace\",\n    \"rules\": [\n      {\n        \"fromPrefix\": \"https://sunset.social/users/\",\n        \"toPrefix\": \"https://dawn.network/u/\"\n      },\n      {\n        \"fromPrefix\": \"https://sunset.social/groups/\",\n        \"toPrefix\": \"https://dawn.network/g/\"\n      },\n      {\n        \"fromPrefix\": \"https://sunset.social/\",\n        \"toPrefix\": \"https://dawn.network/\"\n      }\n    ]\n  }\n}\n
"},{"location":"fep/a427/#5-regex-based-mapping","title":"5. Regex-Based Mapping","text":""},{"location":"fep/a427/#use-case_3","title":"Use Case","text":"

URL structure changed in ways that cannot be expressed with simple prefix substitution:

https://sunset.social/@alice\n\u2192 https://dawn.network/users/alice\n
"},{"location":"fep/a427/#manifest-example_3","title":"Manifest Example","text":"
{\n  \"mapping\": {\n    \"type\": \"RegexReplace\",\n    \"rules\": [\n      {\n        \"pattern\": \"^https://sunset\\\\.social/@([^/]+)$\",\n        \"replacement\": \"https://dawn.network/users/$1\"\n      },\n      {\n        \"pattern\": \"^https://sunset\\\\.social/(.*)$\",\n        \"replacement\": \"https://dawn.network/$1\"\n      }\n    ]\n  }\n}\n

Rules are evaluated in order. The first matching rule is applied.

"},{"location":"fep/a427/#safety-requirements","title":"Safety Requirements","text":"

Regex-based mapping is powerful but carries implementation risks. Peers processing RegexReplace mappings:

Implementations that cannot guarantee linear-time regex matching SHOULD reject RegexReplace mappings and treat the ServerMove as unsupported, falling back to per-actor Move processing.

"},{"location":"fep/a427/#vocabulary","title":"Vocabulary","text":"

This FEP defines the following terms under the namespace https://w3id.org/fep/a427#:

"},{"location":"fep/a427/#types","title":"Types","text":"Term URI Description ServerMigration https://w3id.org/fep/a427#ServerMigration A durable manifest describing a server domain migration ServerMigrationAcceptance https://w3id.org/fep/a427#ServerMigrationAcceptance Confirmation from the destination server accepting the migration ServerMove https://w3id.org/fep/a427#ServerMove Activity notifying peers to process a migration OriginReplace https://w3id.org/fep/a427#OriginReplace Mapping type: replace URI origin, preserve path PrefixReplace https://w3id.org/fep/a427#PrefixReplace Mapping type: replace URI prefixes RegexReplace https://w3id.org/fep/a427#RegexReplace Mapping type: regex-based URI rewriting (RE2 semantics required)"},{"location":"fep/a427/#properties","title":"Properties","text":"Term URI Domain Range Description source https://w3id.org/fep/a427#source ServerMigration, ServerMigrationAcceptance URI Server actor of the source server target https://w3id.org/fep/a427#target ServerMigration, ServerMigrationAcceptance URI Server actor of the destination server mapping https://w3id.org/fep/a427#mapping ServerMigration Object Mapping rules for URI rewriting state https://w3id.org/fep/a427#state ServerMigration String Lifecycle state: active, completed, rolledBack acceptance https://w3id.org/fep/a427#acceptance ServerMigration URI Reference to the ServerMigrationAcceptance migration https://w3id.org/fep/a427#migration ServerMigrationAcceptance URI Reference to the ServerMigration manifest fromOrigin https://w3id.org/fep/a427#fromOrigin OriginReplace String Normalized source origin (scheme + host + port) toOrigin https://w3id.org/fep/a427#toOrigin OriginReplace String Normalized destination origin rules https://w3id.org/fep/a427#rules PrefixReplace, RegexReplace Array Ordered list of replacement rules fromPrefix https://w3id.org/fep/a427#fromPrefix Prefix rule object String URI prefix to match toPrefix https://w3id.org/fep/a427#toPrefix Prefix rule object String Replacement URI prefix pattern https://w3id.org/fep/a427#pattern Regex rule object String RE2-compatible regex pattern replacement https://w3id.org/fep/a427#replacement Regex rule object String Replacement string with capture group references

Note: This specification uses published and updated from the ActivityStreams 2.0 vocabulary (https://www.w3.org/ns/activitystreams#published, https://www.w3.org/ns/activitystreams#updated) rather than defining custom date-time properties.

"},{"location":"fep/a427/#json-ld-context","title":"JSON-LD Context","text":"

The associated context document at https://w3id.org/fep/a427 defines the term mappings:

{\n  \"@context\": {\n    \"fep-a427\": \"https://w3id.org/fep/a427#\",\n    \"ServerMigration\": \"fep-a427:ServerMigration\",\n    \"ServerMigrationAcceptance\": \"fep-a427:ServerMigrationAcceptance\",\n    \"ServerMove\": \"fep-a427:ServerMove\",\n    \"OriginReplace\": \"fep-a427:OriginReplace\",\n    \"PrefixReplace\": \"fep-a427:PrefixReplace\",\n    \"RegexReplace\": \"fep-a427:RegexReplace\",\n    \"source\": { \"@id\": \"fep-a427:source\", \"@type\": \"@id\" },\n    \"target\": { \"@id\": \"fep-a427:target\", \"@type\": \"@id\" },\n    \"mapping\": \"fep-a427:mapping\",\n    \"state\": \"fep-a427:state\",\n    \"acceptance\": { \"@id\": \"fep-a427:acceptance\", \"@type\": \"@id\" },\n    \"migration\": { \"@id\": \"fep-a427:migration\", \"@type\": \"@id\" },\n    \"fromOrigin\": \"fep-a427:fromOrigin\",\n    \"toOrigin\": \"fep-a427:toOrigin\",\n    \"rules\": { \"@id\": \"fep-a427:rules\", \"@container\": \"@list\" },\n    \"fromPrefix\": \"fep-a427:fromPrefix\",\n    \"toPrefix\": \"fep-a427:toPrefix\",\n    \"pattern\": \"fep-a427:pattern\",\n    \"replacement\": \"fep-a427:replacement\"\n  }\n}\n
"},{"location":"fep/a427/#relationship-to-existing-work","title":"Relationship to Existing Work","text":"

This FEP:

"},{"location":"fep/a427/#final-notes","title":"Final Notes","text":"

Server-wide domain migration is inherently messy because identifiers are authority-bound.

This FEP does not attempt to make migration perfect.

It attempts to make it:

"},{"location":"fep/a427/#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/a4ed/","title":"FEP-a4ed: The Fediverse Enhancement Proposal Process","text":"Authors Status Date received Date final Tracking issue Discussions Repository pukkamustard pukkamustard@posteo.net FINAL 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:

"},{"location":"fep/a4ed/#required-sections","title":"Required Sections","text":"

Every FEP should include at least the following sections:

Following sections may be included in a proposal:

"},{"location":"fep/a4ed/#copyright","title":"Copyright","text":"

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 --> [*]
  1. A Fediverse Enhancement Proposal can be submitted by individuals or groups of individuals (authors). See the SUBMISSION.md file for a list of accepted submission methods.
  2. Within seven days one of the facilitators will read and respond to the proposal. The facilitator checks if the proposal conforms to the required structure and fits the scope and objective of the FEPs. The facilitator may request the authors to clarify, justify, or withdraw the proposal. Such a request must not reflect the personal bias of a facilitator. Rather, it will be made strictly to maintain a high quality of submissions. The facilitators reserve the right to reject a submission when a proposal amounts to blatant misuse of the process. The authors may seek feedback from the wider community if the submitted proposal is rejected or clarifications are requested.
  3. If a FEP facilitator approves a submission it receives the status 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.
  4. Registered proposals must have the following metadata attributes: slug, authors, status, dateReceived, trackingIssue and discussionsTo.
  5. When the status is changed to FINAL or WITHDRAWN, facilitator adds dateFinalized or dateWithdrawn attributes to the proposal metadata.
  6. While a proposal has the status DRAFT:
  7. Authors are responsible for initiating community discussion and collecting feedback.
  8. Authors may submit updates to the proposal which will be checked in to the repository by a facilitator. Others may also submit updates, but in this case a change must be approved by at least one author.
  9. Authors may withdraw the submission upon which a facilitator will set the status of the submission to WITHDRAWN.
  10. After at least 60 days the authors may request the proposal to be finalized. This is done by requesting final comments on the proposal.
  11. If there are no community objections within 14 days and the authors can show that they have initiated sufficient awareness and discussion of the proposal, a facilitator will set the status of the submission to FINAL.
  12. If authors have not requested the proposal to be finalized, and there were no updates for 2 years or longer, a facilitator will set the status of the submission to WITHDRAWN.
  13. A proposal with status FINAL can not be changed or updated in a way that would lead to adjustments to implementations. Minor corrections are allowed.
  14. Any substantial change to finalized proposal must be submitted as a separate FEP.

  15. The new FEP MUST include a replaces metadata attribute pointing to FEPs it supersedes.

  16. If the new FEP becomes FINAL, the original one MUST point to it in an added replacedBy metadata attribute.

  17. A proposal with status WITHDRAWN remains in the repository and can be resubmitted.

  18. After a proposal becomes FINAL a facilitator will archive all discussions linked in the tracking issue and add the resulting archive links to the tracking issue.
"},{"location":"fep/a4ed/#facilitators","title":"Facilitators","text":"

A list of facilitators is maintained in the FACILITATORS.md file at the root of the FEP repository.

"},{"location":"fep/a4ed/#submission-methods","title":"Submission Methods","text":"

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.

"},{"location":"fep/a4ed/#fep-process-governance","title":"FEP Process Governance","text":"

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":""},{"location":"fep/a4ed/#copyright_1","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/a5c5/","title":"FEP-a5c5: Web Syndication Methods","text":"Authors Status Date received Tracking issue Discussions Repository AvidSeeker avidseeker7@protonmail.com DRAFT 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.

"},{"location":"fep/a5c5/#syndication-methods","title":"Syndication Methods","text":""},{"location":"fep/a5c5/#creating-feeds","title":"Creating Feeds","text":"

To create a syndication feed, servers must append .rss or .atom to the URL of an object. For example:

"},{"location":"fep/a5c5/#mirrored-profiles","title":"Mirrored Profiles","text":"

For 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:

"},{"location":"fep/a5c5/#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/a974/","title":"FEP-a974: All Actor types should be followable","text":"Authors Status Date received Tracking issue Discussions Repository James Smith james@floppy.org.uk DRAFT 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.

"},{"location":"fep/a974/#motivation","title":"Motivation","text":"

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:

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.

"},{"location":"fep/a974/#impact","title":"Impact","text":"

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.

"},{"location":"fep/a974/#references","title":"References","text":""},{"location":"fep/a974/#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/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.net FINAL 2024-10-19 2025-03-14 #424 Discussions codeberg"},{"location":"fep/ae0c/#summary","title":"Summary","text":"

Relays are important components in 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 wider federated social network, both as consumers and producers of Fediverse content.

Several styles of relays exist in the Activity Fediverse. This FEP describes 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 ActivityPub Follow 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 a 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.

"},{"location":"fep/ae0c/#receiving-messages-from-relays","title":"Receiving Messages from Relays","text":"

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.

"},{"location":"fep/ae0c/#undofollow","title":"Undo/Follow","text":"

Verify that the actor is a known relay client and, if so, remove the client actor from the set of relay actor followers.

"},{"location":"fep/ae0c/#activity-relaying","title":"Activity Relaying","text":"

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.

"},{"location":"fep/ae0c/#mastodon-ld-signatures","title":"Mastodon LD Signatures","text":"

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 and that string is then signed.

"},{"location":"fep/ae0c/#signing-a-json-ld-activity","title":"Signing a JSON-LD Activity","text":"
  1. Create a signature document with only the 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.)
  2. Create a canonical RDF representation of the signature document. This requires JSON-LD expansion using standard algorithms (JSON-LD-ALGO) and conversion to RDF using the Universal RDF Dataset Canonicalization Algorithm 2015 (RDF-CANON). The serialized RDF is then hashed using SHA256 and a hexdigest is created.
  3. Create a SHA256 hex digest for the activity document (without the signature document) using a similar procedure.
  4. Concatenate the SHA256 hex digests for the signature and activity documents and sign the result using SHA256 and the client actor's private key.
  5. Encode the signature using Base64 and set the signatureValue of the signature document to the result.
  6. Set the signature document type to \"RsaSignature2017\".
  7. Set the signature property of the activity to the signature document.
"},{"location":"fep/ae0c/#verifying-a-json-ld-signature","title":"Verifying a JSON-LD Signature","text":"
  1. The signature document is retrieved from the activity and the type is checked to be the nonstandard \"RsaSignature2017\". If not, verification fails.
  2. Save the signatureValue from the signature document.
  3. Remove the 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.
  4. Remove the signature from the activity and generate a SHA256 hex digest for it.
  5. Concatenate the hex digests for the modified signature and activity documents.
  6. Use the client's public key to verify the signature using SHA256.
"},{"location":"fep/ae0c/#litepub-relay-protocol","title":"LitePub Relay Protocol","text":"

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.

"},{"location":"fep/ae0c/#relay-subscription_1","title":"Relay Subscription","text":"

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.

"},{"location":"fep/ae0c/#relay-unsubscription_1","title":"Relay Unsubscription","text":"

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 addressed 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 servers 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.

"},{"location":"fep/ae0c/#other-relay-server-considerations","title":"Other Relay Server Considerations","text":"

The relay server hosting relay actors will have 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 misbehaving clients.

"},{"location":"fep/ae0c/#references","title":"References","text":""},{"location":"fep/ae0c/#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/ae49/","title":"FEP-ae49: Semantic Routing for ActivityPub","text":"Authors Status Type Date received Tracking issue Discussions Repository Steve Bate svc-fep@stevebate.net DRAFT implementation 2026-04-24 #836 Discussions codeberg"},{"location":"fep/ae49/#summary","title":"Summary","text":"

This proposal specifies semantic request routing for ActivityPub servers: an approach to dispatching incoming activities by following the relationships declared within ActivityStreams objects and dereferencing objects using opaque URIs rather than parsing or pattern-matching URL path segments. Semantic routing treats all ActivityPub URIs as opaque identifiers and decouples implementation behavior from any particular URI path structure.

Most existing servers implement \u201cpath-based routing,\u201d where the handler and target actor are determined from path segments such as /users/:username/inbox. This tight coupling makes it hard to migrate between software implementations without breaking URIs and violates the URI Opacity Axiom, since semantics are inferred from paths that are implementation-dependent.

Semantic routing instead relies on dereferencing ActivityStreams objects and using relationships to identify and validate inbox and outbox endpoints. This allows URIs to remain stable across implementations, simplifying migration scenarios where the domain does not change.

"},{"location":"fep/ae49/#motivation","title":"Motivation","text":"

ActivityPub federation works by looking up the target actor\u2019s inbox and POSTing activities to it. The Social API works by POSTing activities to an actor's outbox. ActivityPub documents are dereferenced (GET) using their id (URI).

Most ActivityPub servers route these requests based on a predefined URI path structure. This path-based approach has several drawbacks.

By shifting routing decisions from URI path analysis to graph traversal over ActivityStreams objects, semantic routing eliminates these problems and allows implementations to support flexible URI layouts.

"},{"location":"fep/ae49/#scope","title":"Scope","text":"

This proposal:

The proposal does not modify ActivityPub or ActivityStreams. It adds constraints on implementation strategy while leaving the wire protocol unchanged.

This specification constrains only top-level request handling:

A POST to other URIs can use similar techniques, but since ActivityPub doesn't define these kinds of posts, it is outside the scope of this document.

Internal subhandler selection (for example, by activity type, recipient conditions, or local policy checks) is an implementation detail and is out of scope as long as it is not dependent on a specific URI structure.

"},{"location":"fep/ae49/#terminology","title":"Terminology","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 RFC2119.

path-based routing : A request dispatch strategy in which an HTTP server parses the URI path of an incoming request and matches path segments against a static or parameterized route table to determine the handler responsible for the request. In the ActivityPub context, this typically means extracting an actor identifier (such as a username) from the path and using it to look up the corresponding actor record in a local database, without first dereferencing the request URURIL as an ActivityStreams object.

semantic routing : A request dispatch strategy for ActivityPub servers in which the server processes a request based on the target URI rather than pattern matching on URI path segments. The dispatch uses ActivityPub objects and their relationships to determine how to process the request.

resource : An ActivityStreams object retrieved by dereferencing a resource URI.

opaque URI : A URI whose path, query, and fragment components carry no meaning to recipients beyond what is logically needed to dereference the URI or process POST requests to it RFC3986.

top-level handler : One of the three request classes in scope for this specification: inbox POST, outbox POST, or object dereference GET.

"},{"location":"fep/ae49/#conformance","title":"Conformance","text":"

This specification defines requirements for one conformance class:

Semantically Routing Server : An ActivityPub server (federated server or combined server) that dispatches incoming HTTP requests exclusively through semantic routing as defined in this document.

All of Section requirements are normative. All examples, notes, and sections explicitly labelled \"non-normative\" are informative.

Note: Implementations that satisfy the requirements in this document remain fully conformant with ActivityPub. The requirements here constrain how routing is performed, not what is placed on the wire.

"},{"location":"fep/ae49/#path-based-routing-characteristics-and-problems","title":"Path-Based Routing: Characteristics and Problems","text":""},{"location":"fep/ae49/#how-path-based-routing-works","title":"How Path-Based Routing Works","text":"

In a typical fediverse server using path-based routing, a web framework (such as Rails, Django, Express, or Flask) is configured with a route table mapping URI path patterns to controller functions. When an ActivityPub activity arrives addressed to a local actor, the server:

  1. Receives an HTTP POST to a URI such as https://social.example/users/alice/inbox.
  2. Matches the path /users/alice/inbox against the route pattern /users/:username/inbox.
  3. Extracts the string alice as the username.
  4. Looks up actor alice in the local database.
  5. Passes the request body to the inbox handler with alice's database record.

A representative path-based route table from a pseudocode web framework:

POST /users/:username/inbox          \u2192 inbox_controller(username)\nGET  /users/:username/outbox         \u2192 outbox_controller(username)\nGET  /users/:username                \u2192 actor_controller(username)\nGET  /users/:username/followers      \u2192 followers_controller(username)\nGET  /users/:username/following      \u2192 following_controller(username)\n

The critical characteristic is that the username portion of the path is authoritative for dispatch.

"},{"location":"fep/ae49/#problems-introduced-by-path-based-routing","title":"Problems Introduced by Path-Based Routing","text":""},{"location":"fep/ae49/#uri-lock-in-and-migration-failure","title":"URI Lock-In and Migration Failure","text":"

Because the routing table defines the valid URI patterns for actor endpoints, changing software means either:

Neither option is satisfactory. The ActivityPub specification provides no URI migration mechanism precisely because URIs are supposed to be stable, opaque identifiers.

Example: migration failure with path-based request routing:

A community server running SoftwareA uses actor URIs of the form https://community.example/users/alice, with inbox at /users/alice/inbox.

The community migrates to SoftwareB, which uses actor URIs of the form https://community.example/alice, with inbox at /inboxes/alice.

All existing actor URIs are now invalid. Remote servers that delivered activities to /users/alice/inbox receive HTTP 404 responses. The actor's followers on remote servers reference the old actor id, which no longer resolves.

If both SoftwareA and SoftwareB implemented semantic request routing, the migration would be a simple transfer of content with not changes to URIs.

"},{"location":"fep/ae49/#requirements-for-semantic-routing","title":"Requirements for Semantic Routing","text":""},{"location":"fep/ae49/#uri-opacity","title":"URI Opacity","text":"

URI opacity is the foundation of semantic routing. Routing decisions MUST NOT depend on the syntactic structure of a URI's path, query, or fragment components.

A Semantically Routing Server MUST NOT parse the path, query string, or fragment of an incoming request URI to determine the identity of the target actor or the type of endpoint being accessed (inbox, outbox, etc.).

"},{"location":"fep/ae49/#http-request-handling","title":"HTTP Request Handling","text":"

When a Semantically Routing Server receives an HTTP request, it MUST NOT depend on the syntactic structure of a URI's path, query, or fragment components to handle it.

"},{"location":"fep/ae49/#semantic-routing-algorithms","title":"Semantic Routing Algorithms","text":"

This section is non-normative.

Although it may be possible to implement semantic routing using other algorithms, this section shows pseudo-code for a possible implementation. This proposal does not include details like authorization or other security considerations that are not uniquely related to semantic request routing.

"},{"location":"fep/ae49/#http-get","title":"HTTP GET","text":""},{"location":"fep/ae49/#http-post","title":"HTTP POST","text":"

Example:

POST /x/9f3c1 HTTP/1.1\nHost: social.example\nContent-Type: application/activity+json\n\n{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"id\": \"https://remote.example/activities/abcd\",\n  \"type\": \"Follow\",\n  \"actor\": \"https://remote.example/users/bob\",\n  \"object\": \"https://social.example/actors/alice\"\n}\n

Where https://social.example/x/9f3c1 is an opaque URI that happens to be Alice's inbox, the server can process the request semantically as follows:

  1. Treat the request target URI as an opaque identifier and do not parse path segments.
  2. Dereference https://social.example/x/9f3c1 to obtain the local inbox resource.
  3. Identify the owning actor by reading resource relations (for example, attributedTo), or by resolving reverse links from actor resources that reference this inbox.
  4. Confirm the resolved actor document contains an inbox equal to https://social.example/x/9f3c1.
  5. Dispatch to the inbox handler for that actor and continue normal ActivityPub processing of the posted activity (type: Follow, authorization checks, policy checks, delivery side-effects, and response status).

This algorithm works regardless of whether the inbox URI looks like /users/alice/inbox, /inbox/42, or /x/9f3c1.

The same procedure is used for POST requests to outbox URIs.

"},{"location":"fep/ae49/#shared-inbox","title":"Shared Inbox","text":"

For sharedInbox POST, the algorithm is similar but applied to each local inbox URI targeted for delivery.

For sharedInbox GET, the opaque URI is used to identify the request target. The dereferencing or construction of the resulting OrderedCollection is implementation-dependent.

"},{"location":"fep/ae49/#security-considerations","title":"Security Considerations","text":"

TODO

"},{"location":"fep/ae49/#related-work","title":"Related Work","text":""},{"location":"fep/ae49/#solid","title":"Solid","text":"

This section is non-normative.

The Solid Protocol similarly treats resource URIs as opaque and uses link relations to discover associated resources. Solid's approach to data pods, where access control and resource discovery are driven by linked metadata rather than URI structure parallels the motivation for semantic routing in ActivityPub.

"},{"location":"fep/ae49/#implementations","title":"Implementations","text":""},{"location":"fep/ae49/#references","title":"References","text":""},{"location":"fep/ae49/#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/ae97/","title":"FEP-ae97: Client-side activity signing","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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":"
sequenceDiagram\n  participant Client\n  participant Server\n  Note right of Client: Client generates signing keys and creates an actor\n  Client ->> Server: GET /.well-known/apgateway\n  Server ->> Client: Returns information about itself\n  Client ->> Server: POST /.well-known/apgateway\n  Note left of Server: Server creates an account for the actor\n  Server ->> Client: Returns HTTP signature keys\n  Note right of Client: Client adds server's keys to the actor\n  Client ->> Server: POST Update(Actor) to outbox

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 actor is already registered, the server MUST return the same keys in a response with a 200 OK status code.

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. However, the client MUST NOT use key identifiers provided by the server (if they were present in the server's response).

After updating the actor object, the client MUST publish an Update activity for it.

"},{"location":"fep/ae97/#sending-activities","title":"Sending activities","text":"

Activities generated by a client MUST adhere to ActivityPub server-to-server protocol specification.

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.

"},{"location":"fep/ae97/#receiving-activities","title":"Receiving activities","text":"

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.

Clients MUST verify received portable activities. Non-portable activities MAY be accepted without verification.

"},{"location":"fep/ae97/#media-api","title":"Media API","text":""},{"location":"fep/ae97/#upload-media","title":"Upload media","text":"

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.

"},{"location":"fep/ae97/#delete-media","title":"Delete media","text":"

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.

"},{"location":"fep/ae97/#security-considerations","title":"Security considerations","text":"

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":""},{"location":"fep/ae97/#references","title":"References","text":""},{"location":"fep/ae97/#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/b06c/","title":"FEP-b06c: ActivityPoll","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.org DRAFT 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.

  1. Activities are not delivered in near-real-time; there is a potential delay, up to the polling interval of the consumer (which may be variable).
  2. The consumer can request updates when no new activities have been published, which takes both consumer and publisher resources.
  3. Activities are not reliably delivered to the addressees. Unless the addressees' servers poll the feed, the addressees do not receive the activities in their 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:

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.

"},{"location":"fep/b06c/#outbox","title":"Outbox","text":"

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.

"},{"location":"fep/b06c/#authentication","title":"Authentication","text":"

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.

"},{"location":"fep/b06c/#lack-of-activity-side-effects","title":"Lack of activity side effects","text":"

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:

Publishers 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.

"},{"location":"fep/b06c/#consumers","title":"Consumers","text":"

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.

"},{"location":"fep/b06c/#following","title":"Following","text":"

ActivityPoll consumers SHOULD include remote ActivityPoll actors in their actors' following collections.

"},{"location":"fep/b06c/#polling-frequency","title":"Polling frequency","text":"

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.

"},{"location":"fep/b06c/#local-delivery","title":"Local delivery","text":"

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.

"},{"location":"fep/b06c/#activity-side-effects","title":"Activity side effects","text":"

Some activities, or their objects, MAY be added to special collections on the consumer server. These include:

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.

"},{"location":"fep/b06c/#pollonly","title":"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.

"},{"location":"fep/b06c/#updateinterval","title":"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:

This 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":""},{"location":"fep/b06c/#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/b2b8/","title":"FEP-b2b8: Long-form Text","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.org DRAFT 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.

"},{"location":"fep/b2b8/#properties","title":"Properties","text":""},{"location":"fep/b2b8/#id","title":"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.

"},{"location":"fep/b2b8/#name","title":"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 &amp; or &lt; should be used.

This property provides the same functionality as the title property in RSS 2.0.

"},{"location":"fep/b2b8/#url","title":"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.

"},{"location":"fep/b2b8/#summary_1","title":"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.

"},{"location":"fep/b2b8/#attributedto","title":"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.

"},{"location":"fep/b2b8/#published","title":"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.

"},{"location":"fep/b2b8/#updated","title":"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.

"},{"location":"fep/b2b8/#image","title":"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.

"},{"location":"fep/b2b8/#content","title":"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:

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.

"},{"location":"fep/b2b8/#source","title":"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.

"},{"location":"fep/b2b8/#replies","title":"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.

"},{"location":"fep/b2b8/#inreplyto","title":"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.

"},{"location":"fep/b2b8/#attachment","title":"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.

"},{"location":"fep/b2b8/#tag","title":"tag","text":"

The tag property provides additional metadata about the text. There are two important types of tags:

"},{"location":"fep/b2b8/#context","title":"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.

"},{"location":"fep/b2b8/#generator","title":"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.

"},{"location":"fep/b2b8/#preview","title":"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.

"},{"location":"fep/b2b8/#to-cc-bcc-bto-audience","title":"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.

"},{"location":"fep/b2b8/#sensitive","title":"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:

"},{"location":"fep/b2b8/#examples","title":"Examples","text":"

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.

"},{"location":"fep/b2b8/#in-stream-with-image","title":"In stream, with image","text":"

An example of a long-form text object with an image property displayed in a social stream.

"},{"location":"fep/b2b8/#in-stream-without-image","title":"In stream, without image","text":"

An example of a long-form text object without an image property displayed in a social stream.

"},{"location":"fep/b2b8/#in-stream-no-title","title":"In stream, no title","text":"

An example of a long-form text object without a name property displayed in a social stream.

"},{"location":"fep/b2b8/#in-stream-sensitive-content","title":"In stream, sensitive content","text":"

An example of a long-form text object with a sensitive property displayed in a social stream with a content warning.

"},{"location":"fep/b2b8/#in-stream-with-preview","title":"In stream, with preview","text":"

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.

"},{"location":"fep/b2b8/#references","title":"References","text":""},{"location":"fep/b2b8/#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/bad1/","title":"FEP-bad1: Object history collection","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 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.

"},{"location":"fep/bad1/#defining-the-history-special-collection","title":"Defining the 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.

"},{"location":"fep/bad1/#terms-defined-by-this-fep","title":"Terms defined by this FEP","text":"

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":""},{"location":"fep/bad1/#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/baf5/","title":"FEP-baf5: Administrator Collection","text":"Authors Status Date received Tracking issue Discussions Repository Julian Lam julian@nodebb.org DRAFT 2026-05-22 #848 Discussions codeberg"},{"location":"fep/baf5/#summary","title":"Summary","text":"

This FEP introduces a mechanism for discovering the administrators of an ActivityPub instance. It extends the \"Group Moderator\" pattern from FEP 1b12 and the \"Application Actor\" concept from FEP d556 by defining an OrderedCollection of administrators referenced from the instance's application actor.

"},{"location":"fep/baf5/#what-is-an-administrator","title":"What is an administrator?","text":"

An administrator is a user who has carte blanche access and permission to federate moderation-style actions (object updates, deletion, etc.) on behalf of other users on the same domain. This aligns with the origin-based security model described in FEP fe34, where such actions are considered authentic when the actor and the affected parties share the same origin.

This is distinct from a moderator, whose privileges are scoped to a specific group or community (see FEP 1b12). Administrators have instance-wide authority; moderators do not.

Note

ActivityPub implementations may not have the concept of groups at all. In such cases, administrators are the only mechanism for delegated moderation authority. Examples of hierarchical structures across implementations:

  1. NodeBB: Post \u2192 Topic \u2192 Category
  2. Lemmy/Piefed: Comment \u2192 Post \u2192 Community
"},{"location":"fep/baf5/#why-is-this-needed","title":"Why is this needed?","text":"

FEP fe34 implicitly infers moderator privilege for any same-origin actor carrying out destructive actions (update, delete, etc.) against another user on the same instance. In effect, it delegates the responsibility of determining boundaries between users to the sending instance.

This is not necessarily insecure, but without a standardized mechanism to declare those boundaries, receivers have no way to inspect or verify the authorization hierarchy on the sending side. This FEP (in conjunction with the \"Group Moderation\" pattern in FEP 1b12) makes these boundaries explicit and discoverable.

"},{"location":"fep/baf5/#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/baf5/#instance-actor-and-application-actor","title":"Instance Actor and Application Actor","text":"

An ActivityPub server MUST publish an Application Actor representing the instance itself.

The instance's application actor MUST include an attributedTo property referencing an OrderedCollection containing the instance's administrators.

{\n    \"@context\": \"https://www.w3.org/ns/activitystreams\",\n    \"id\": \"https://example.social/actor\",\n    \"type\": \"Application\",\n    \"name\": \"Example Instance\",\n    \"attributedTo\": \"https://example.social/administrators\"\n}\n
"},{"location":"fep/baf5/#administrators-collection","title":"Administrators Collection","text":"

The attributedTo property MUST reference an Collection (or CollectionPage for paginated collections). Each item in the collection MUST be either:

  1. A URI reference to a User Actor (e.g., https://example.social/users/admin), or
  2. A User Actor object representing an administrator.

Both forms are acceptable and receivers MAY support both.

{\n    \"@context\": \"https://www.w3.org/ns/activitystreams\",\n    \"id\": \"https://example.social/administrators\",\n    \"type\": \"Collection\",\n    \"items\": [\n        \"https://example.social/users/alice\",\n        \"https://example.social/users/bob\"\n    ]\n}\n
"},{"location":"fep/baf5/#collection-management","title":"Collection Management","text":"

The administrators collection MAY be modified with Add and Remove activities, following the same pattern described in FEP 1b12.

{\n    \"@context\": \"https://www.w3.org/ns/activitystreams\",\n    \"type\": \"Announce\",\n    \"actor\": \"https://example.social/actor\",\n    \"id\": \"https://example.social/activities/announce/1\",\n    \"object\": {\n        \"@context\": \"https://www.w3.org/ns/activitystreams\",\n        \"type\": \"Add\",\n        \"actor\": \"https://example.social/users/alice\",\n        \"id\": \"https://example.social/activities/add/1\",\n        \"object\": \"https://example.social/users/bob\",\n        \"target\": \"https://example.social/administrators\"\n    }\n}\n

Either activity MAY be treated as a cache invalidation forcing the re-fetch of the Administrator Collection.

"},{"location":"fep/baf5/#discovery","title":"Discovery","text":"

Support for this FEP is indicated by the implementation of FEP 844e, where the implements array of the instance's Application Actor includes:

{\n    \"href\": \"https://w3id.org/fep/baf5\",\n    \"name\": \"FEP-baf5: Administrator Collection\"\n}\n
"},{"location":"fep/baf5/#security-and-authorization","title":"Security and Authorization","text":"

This FEP supersedes the same-origin assumption described in FEP fe34. The administrators collection (in conjunction with the FEP 1b12 moderator collection if applicable) determines whether a same-origin or cross-origin actor has administrative privilege.

When verifying the whether a moderation action (such as Update, Delete, or other object mutations) is authorized, a receiver SHOULD:

  1. Resolve the actor of the activity.
  2. Verify that the sending instance supports the Administrator Collection (this FEP), as indicated by FEP 844e. If the FEP is not advertised, fall back to the standard origin-based security model as defined in FEP fe34.
  3. Resolve the attributedTo of the instance's Application Actor to obtain the administrators collection.
  4. Check whether the actor is listed as an item in the administrators collection.
  5. If the actor is present, the action is authentic and MAY be processed.

Note

Receivers MAY cache the administrators collection to reduce network demand.

"},{"location":"fep/baf5/#fallback-behavior","title":"Fallback Behavior","text":"

If the sending instance does not support the Administrator Collection (this FEP), the receiver SHOULD fall back to the standard origin-based security model as defined in FEP fe34.

"},{"location":"fep/baf5/#implementors","title":"Implementors","text":""},{"location":"fep/baf5/#references","title":"References","text":""},{"location":"fep/baf5/#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/bebd/","title":"FEP-bebd: Follow Invites","text":"Authors Status Date received Tracking issue Discussions Repository MaddyUnderStars maddyunderstars@aus.social DRAFT 2026-06-05 #854 Discussions codeberg"},{"location":"fep/bebd/#summary","title":"Summary","text":"

This document describes an alternative method of accepting follow requests via an 'invite code' intended to be used with Private FEP-1b12 Groups, although it is applicable to any Actor. It further defines an extension of Webfinger to resolve an InviteCode to its corresponding Actor.

"},{"location":"fep/bebd/#why-is-this-needed","title":"Why is this needed?","text":"

It is useful to allow some mechanism of joining communities via a secret rather than knowing the Actor ID and having manually accepted requests.

Some use cases include: - As an anti-spam measure - To lift some burden of Accepting Follow requests manually - The ability to Follow Actors via short identifiers rather than a full Actor ID or Webfinger mention. - The ability to restrict certain InviteCodes - The ability to provide a tentative inviteCode to a group without establishing a full Follow relationship

"},{"location":"fep/bebd/#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/bebd/#invitecodes","title":"InviteCodes","text":"

InviteCode is an ActivityStreams Object that represents a short code used to automatically Approve Follow requests.

It MUST contain the following properties: - type (REQUIRED): The value of the type property MUST be the string InviteCode - name (REQUIRED): The instance-unique string used to reference this InviteCode - attributedTo (REQUIRED): The Actor that this InviteCode can be used to Follow

The document MAY contain additional properties.

The InviteCode name MAY be user-defined. It is RECOMMENDED that the name is a short, alphanumeric string. If InviteCodes are to be dereferenceable via Webfinger as described below, the name MUST be able to form a valid URL.

As InviteCodes can be dereferenced via Webfinger, they MUST be unique per instance.

"},{"location":"fep/bebd/#follow-activities-using-an-invitecode","title":"Follow Activities using an InviteCode","text":"

A Follow activity MAY include the ID of an InviteCode in the instrument field.

{\n    \"id\": \"https://example.com/myFollowActivity\",\n    \"type\": \"Follow\",\n    \"actor\": \"https://example.com/myActor\",\n    \"object\": \"https://remote.example/remoteActor\",\n    \"instrument\": \"https://remote.example/remoteInvite\",\n}\n

When an Actor receives a Follow activity containing an InviteCode: 1. If the Actor does not manually approve follows, the InviteCode is ignored 2. The Actor MUST ensure that the InviteCode exists and was acknowledged by the Actor prior to its use 3. The Actor MAY impose any additional restrictions to the InviteCodes use 4. If the InviteCode has been deemed valid, an Accept activity is automatically sent for the Follow as would be normal for an Actor not gated by an InviteCode.

If the InviteCode is not valid, a Reject activity SHOULD be sent.

"},{"location":"fep/bebd/#example-restrictions","title":"Example Restrictions","text":"

This section is non-normative

"},{"location":"fep/bebd/#activities-on-invitecodes","title":"Activities on InviteCodes","text":"

For the purposes of Group moderation by external Actors is it useful to allow modifications to InviteCodes via activities.

When an activity modifying the InviteCodes for an Actor is received, the Actor MUST ensure that the activity is being performed by an authorised Actor.

When an Add, Update, or Remove activity is received by an Actor from an unauthorised Actor, the Actor MAY send a Reject(InviteCode), refuse to add it to its invites Collection, and deem the InviteCode invalid for future Follow requests.

"},{"location":"fep/bebd/#invites-collection","title":"invites Collection","text":"

Actors that accept InviteCodes SHOULD include an invites field that resolves to a Collection containing valid InviteCodes for this Actor. If present, the invites Collection MUST be private and only accessible to authorised Actors.

"},{"location":"fep/bebd/#invitecode-dereferencing-via-webfinger","title":"InviteCode Dereferencing via Webfinger","text":"

InviteCodes MAY be dereferenceable via Webfinger.

To prevent InviteCodes colliding with regular Webfinger acct lookups, a new URI scheme invite is defined.

To resolve an InviteCode ABCDE hosted on instance example.com, we can query:

GET https://example.com/.well-known/webfinger?resource=invite:ABCDE@example.com\n

This will resolve to an InviteCode object which can further be resolved via the attributedTo field to the target Actor.

"},{"location":"fep/bebd/#a-note-on-authorised-actors","title":"A note on Authorised Actors","text":"

This section is non-normative

For the purposes of viewing the invites collection, authorised actors may include only the 'invite-actor', or it may include anyone following them, or any other criteria. For the purposes of updating, adding, or removing InviteCodes from Actors, authorised actors may be the same set as above but that is not required.

"},{"location":"fep/bebd/#references","title":"References","text":""},{"location":"fep/bebd/#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 neighbouring rights to this work.

"},{"location":"fep/c07e/","title":"FEP-c07e: add product type to object","text":"Authors Status Type Date received Tracking issue Discussions Repository potato @potatomeow@fosstodon.org DRAFT implementation 2026-03-15 #786 Discussions codeberg"},{"location":"fep/c07e/#summary","title":"Summary","text":"

add a new type \"Product\" into \"Object\", alongside with \"Note\", \"Article\", etc.

"},{"location":"fep/c07e/#scenario-1","title":"Scenario 1","text":"

alice: i want to show a list of thinkpads that are for sales in my instance. i want to buy a used one for myself.

alice: (opens a client app, search for the keyword thinkpad)

alice: (mildly confused) huh, i am seeing a list of toots about thinkpad upgrades and modifications. this is not what i want to see. i intend to make a purchase.

alice: (i wish there is a way to differentiate the type of toots i want to see.)

"},{"location":"fep/c07e/#scenario-2","title":"Scenario 2","text":"

bob: i am going to find some recipes about baked potatoes. i want to make some baked potatoes for lunch at home.

bob: (opens a client app, search for the keyword baked_potato)

bob: (mildly confused) huh, i am seeing a list of toots about baked potato dishes offered by restaurants nearby. this is not what i want to see, because i don't intend to buy anything.

bob: (i wish there is a way to differentiate the type of toots i want to see.)

"},{"location":"fep/c07e/#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/c0e0/","title":"FEP-c0e0: Emoji reactions","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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.

"},{"location":"fep/c0e0/#undo-reaction","title":"Undo reaction","text":"

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.

"},{"location":"fep/c0e0/#implementations","title":"Implementations","text":"

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:

"},{"location":"fep/c0e0/#references","title":"References","text":""},{"location":"fep/c0e0/#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/c118/","title":"FEP-c118: Content licensing support","text":"Authors Status Date received Tracking issue Discussions Repository Tim Bray tbray@texgtuality.com DRAFT 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:

  1. Prevent access to posts without the accessor having somehow acknowledged the content license that applies.
  2. Build a menu of content-license choices, probably starting from a Creative Commons basis.
  3. One parameter of the content licenses should be temporal; i.e., license access to posts but only for two weeks (or some other interval).
  4. Make it easy and straightforward for individuals posting to the Fediverse to pick a default content license for their posts, and also to apply other licensing choices to individual posts.
  5. Arrange that when one user follows another, the following user must acknowledge the default content licensing options of the followed user.
  6. Since most users will just take the defaults, each instance should carefully choose and prominently display its content-licensing defaults.

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":""},{"location":"fep/c118/#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/c16b/","title":"FEP-c16b: Formatting MFM functions","text":"Authors Status Date received Tracking issue Discussions Repository ilja ilja@ilja.space DRAFT 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.

"},{"location":"fep/c16b/#history","title":"History","text":"

(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.

"},{"location":"fep/c16b/#mfm-functions","title":"MFM Functions","text":"

(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].

"},{"location":"fep/c16b/#examples","title":"Examples","text":"

(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.

"},{"location":"fep/c16b/#examples_1","title":"Examples","text":"

(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.

"},{"location":"fep/c16b/#discovery","title":"Discovery","text":"

(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.

"},{"location":"fep/c16b/#example","title":"Example","text":"

(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":""},{"location":"fep/c16b/#references","title":"References","text":""},{"location":"fep/c16b/#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/c180/","title":"FEP-c180: Problem Details for ActivityPub","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.org DRAFT 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":""},{"location":"fep/c180/#specification","title":"Specification","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.

"},{"location":"fep/c180/#problem-types-for-activitypub","title":"Problem types for ActivityPub","text":"

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":"

This 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":"

The 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":"

The 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":"

The 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":"

The 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":"

The 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.

"},{"location":"fep/c180/#principal-actor-mismatch","title":"Principal-actor mismatch","text":"

The 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.

"},{"location":"fep/c180/#actor-not-authorized","title":"Actor not authorized","text":"

The 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":"

This 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":"

This 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":""},{"location":"fep/c180/#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/c195/","title":"FEP-c195: JSONPath Filtering for ActivityPub Collection Retrieval","text":"Authors Status Type Date received Tracking issue Discussions Repository Steve Bate svc-fep@stevebate.net DRAFT implementation 2026-03-17 #790 Discussions codeberg"},{"location":"fep/c195/#summary","title":"Summary","text":"

This proposal defines a filter syntax for ActivityPub collection retrieval.

The syntax supports:

Note

This proposal is based on the standard RFC9535 JSONPath language. Some JSONPath libraries implement extended functionality beyond the scope of this FEP.

"},{"location":"fep/c195/#terminology","title":"Terminology","text":"

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 BCP 14 RFC2119 and RFC8174 when, and only when, they appear in all capitals, as shown here.

"},{"location":"fep/c195/#filter-syntax","title":"Filter Syntax","text":""},{"location":"fep/c195/#grammar","title":"Grammar","text":"

Filter expressions MUST use JSONPath syntax defined by RFC9535.

Implementations of this proposal MUST accept at least:

See also: examples.

"},{"location":"fep/c195/#evaluation-model","title":"Evaluation Model","text":"

A viewable object is defined as an ActivityPub entity:

A server applying this proposal:

  1. Retrieves the viewable items from the addressed collection or the shared inbox.
  2. Applies the JSONPath filter expression to candidate activity/object items
  3. Returns matched item values in collection order for OrderedCollection or subtypes.

The filter is applied to collection items, not to the outer collection object.

"},{"location":"fep/c195/#http-usage","title":"HTTP Usage","text":"

This syntax is used for the filter parameter on collection retrieval endpoints. The filter is an item-level filter. Depending on the collection type it will be applied to orderedItems or items.

The item filter has two variations:

Case Description No leading [ The expression will be wrapped in [ and ] Leading [ Used as-is for the item filter expresssion

Example

@.type == 'Create'

becomes

$[?@type == 'Create']

(for an OrderedCollection, otherwise items will be used)

For filtering with additional features like projection, the query should start with \"[\".

Example

[?@.object[*].attachment[?@.type == 'Video']].id

becomes

$[?@.object[*].attachment[?@.type == 'Video']].id]

"},{"location":"fep/c195/#query-results","title":"Query Results","text":"

The results MUST be returned as ActivityPub collection objects (optionally paged). The result collection URI should be unique to that filtering operation rather than the original collection URI. For example, it could be original URI with the filter criteria query argument or a generated URI.

"},{"location":"fep/c195/#example-filters","title":"Example Filters","text":"
# Get all CRUD activities\n@.type == 'Create' || @.type == 'Update' || @.type == 'Delete'\n\n# Get all notification (non-CRUD) activities\n!(@.type == 'Create' || @.type == 'Update' || @.type == 'Delete')\n\n# Get all activities where Alice is the actor\n@.actor=='https://example.social/users/alice'\n\n# Get all items published before a specified date\n@.published<='2026-02-20T10:30:00Z'\n\n# Get the URI of the objects of all activities having a Video attachment\n# This also peforms \"projection\" of the \"id\" property\n[?@.object[*].attachment[?@.type == 'Video']].id\n\n# Get all activities related to the given object URI (handles refs or embedded)\n# Uses \"id\" projection\n[?@.object == 'https://example.social/objects/note-1' || \\\n  @..object[?@ == 'https://example.social/objects/note-1']].id\n\n# Get all activities who have an actor with 'bob' or 'Bob' in the name\nsearch(@.actor, '[Bb]ob')\n

HTTP Example

GET /actor/inbox?filter=%24.orderedItems%5B%3F%28%40.type%20%3D%3D%20%27Create%27%20%7C%7C%20%40.type%20%3D%3D%20%27Update%27%20%7C%7C%20%40.type%20%3D%3D%20%27Delete%27%29%5D\nAccept: application/activity+json\n\nHTTP/1.1 200 OK\nContent-Type: application/activity+json\n\n{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"id\": \"https://example.com/actor/inbox?filter=%24.orderedItems%5B%3F%28%40.type%20%3D%3D%20%27Create%27%20%7C%7C%20%40.type%20%3D%3D%20%27Update%27%20%7C%7C%20%40.type%20%3D%3D%20%27Delete%27%29%5D\nAccept: application/activity+json\",\n  \"type\": \"OrderedCollectionPage\",\n  \"totalItems\": 3,\n  \"partOf\": \"https://example.com/actor/inbox\",\n  \"next\": \"https://example.com/actor/inbox?offset=2&filter=%24.orderedItems%5B%3F%28%40.type%20%3D%3D%20%27Create%27%20%7C%7C%20%40.type%20%3D%3D%20%27Update%27%20%7C%7C%20%40.type%20%3D%3D%20%27Delete%27%29%5D\",\n  \"orderedItems\": [\n    {\n      ...\n    }\n}\n
"},{"location":"fep/c195/#error-handling","title":"Error Handling","text":"

Invalid filters MUST result in an HTTP client error status. Examples include:

Implementations MAY include parser or feature details in a problem response body.

"},{"location":"fep/c195/#related-feps","title":"Related FEPs","text":""},{"location":"fep/c195/#fep-34c1-collection-filtering-using-tree-hypermedia-vocabulary","title":"FEP-34c1: Collection Filtering using TREE Hypermedia Vocabulary","text":"

This FEP is similar in intent but more oriented to RDF and Linked Data use cases.

"},{"location":"fep/c195/#fep-6606-activitypub-client-to-server-collections-addressing-conventions","title":"FEP-6606: ActivityPub client to server collections addressing conventions","text":"

A simpler, but less powerful, filtering language based on a URL query parameter-based language defined in the FEP. The \"fuzzy\" matching is not well-defined (seems to be substring match) and it appears to not be able to handle conjunctions.

"},{"location":"fep/c195/#fep-bad1-object-history-collection","title":"FEP-bad1: Object history collection","text":"

Filtering some collections (e.g., inbox and outbox) can provide similar historical results for a given object without the need for explicit history collections.

"},{"location":"fep/c195/#implementations","title":"Implementations","text":""},{"location":"fep/c195/#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/c390/","title":"FEP-c390: Identity Proofs","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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":""},{"location":"fep/c390/#identity-proofs","title":"Identity proofs","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:

The document MAY contain additional properties.

Identity proofs SHOULD be attached to an actor object, under the attachment property.

"},{"location":"fep/c390/#proof-generation","title":"Proof generation","text":"

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.

"},{"location":"fep/c390/#implementation-notes","title":"Implementation notes","text":"

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":""},{"location":"fep/c390/#references","title":"References","text":""},{"location":"fep/c390/#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/c4ad/","title":"FEP-c4ad: Viewership History","text":"Authors Status Date received Tracking issue Discussions Repository AvidSeeker avidseeker7@protonmail.com DRAFT 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:

"},{"location":"fep/c4ad/#querying-viewership-records","title":"Querying Viewership Records","text":"

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.co DRAFT 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":""},{"location":"fep/c551/#context","title":"Context","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.

"},{"location":"fep/c551/#overview","title":"Overview","text":"

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.

"},{"location":"fep/c551/#test-specifications","title":"Test Specifications","text":"

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].

"},{"location":"fep/c551/#test-functions","title":"Test Functions","text":"

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).

"},{"location":"fep/c551/#test-inputs","title":"Test Inputs","text":"

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 }.

Conformance requirements are indicated by sentences containing MUST a la RFC2119."},{"location":"fep/c551/#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/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":""},{"location":"fep/c551/tests/#usage","title":"Usage","text":""},{"location":"fep/c551/tests/#running-local-test-files-via-data-url","title":"Running local test files via Data URL","text":"
activitypub-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 Type Date received Tracking issue Discussions Repository Lynn Foster lynn@mikorizal.org DRAFT implementation 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.

Example:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    {\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\": \"vf:hasUnit\",\n      \"hasNumericalValue\": \"vf:hasNumericalValue\",\n      \"action\": \"vf:action\"\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    \"due\": \"2024-05-23T17:00:00.000000Z\",\n    \"action\": \"work\",\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:

{\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      \"action\": \"vf:action\",\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    \"effortQuantity\": {\n      \"hasUnit\": \"hour\",\n      \"hasNumericalValue\": \"1.5\"\n    },\n    \"provider\": \"https://project.example/actors/bob\",\n    \"receiver\": \"https://project.example/actors/alice\",\n    \"content\": \"Looks great, I left a few comments.\",\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":""},{"location":"fep/c5a1/#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/c648/","title":"FEP-c648: Blocked Collection","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@prodromou.name DRAFT 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.

"},{"location":"fep/c648/#motivation","title":"Motivation","text":"

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.

"},{"location":"fep/c648/#user-stories","title":"User stories","text":""},{"location":"fep/c648/#context","title":"Context","text":"

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.

"},{"location":"fep/c648/#version-stamped-context-urls","title":"Version-stamped context URLs","text":"

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 Purpose https://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.

"},{"location":"fep/c648/#security-considerations","title":"Security considerations","text":"

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.

"},{"location":"fep/c648/#implementations","title":"Implementations","text":"

The onepage.pub server implements the blocked collection.

"},{"location":"fep/c648/#history","title":"History","text":"

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.

"},{"location":"fep/c648/#references","title":"References","text":""},{"location":"fep/c648/#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/c7d3/","title":"FEP-c7d3: Ownership","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository silverpill @silverpill@mitra.social WITHDRAWN 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.

"},{"location":"fep/c7d3/#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/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:

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:

  1. It was fetched from the location that has the same origin as its owner's ID.
  2. It was delivered to inbox and the POST request contained a valid HTTP signature created using a key whose owner has the same origin as the object owner.
  3. It contains a valid FEP-8b32 integrity proof created using a key whose owner has the same origin as the object owner.
  4. If it is embedded within another object, and its owner has the same origin as the owner of the containing object.

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:

"},{"location":"fep/c7d3/#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/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.

"},{"location":"fep/c7d3/#references","title":"References","text":""},{"location":"fep/c7d3/#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/c81b/","title":"FEP-c81b: Agent Social Attribution for ActivityPub","text":"Authors Status Date received Tracking issue Discussions Repository Aron Price aron@lessersoul.ai DRAFT 2026-03-18 #798 Discussions codeberg"},{"location":"fep/c81b/#summary","title":"Summary","text":"

This proposal defines agentAttribution, a post-level ActivityPub extension for disclosing when a Note was authored or materially produced by an AI agent acting under delegated authority. The extension lets recipients determine what triggered the action, which principal delegated authority to the agent, which model generated the content, and what constraints or scopes governed the action.

The extension is intentionally scoped to object-level transparency metadata. Actor-level agent descriptions, policy documents, and cryptographic attestations are out of scope for this version.

"},{"location":"fep/c81b/#history","title":"History","text":"

Lesser introduced per-post agent attribution to support transparent agent-authored social actions in ActivityPub payloads and client APIs. Earlier implementation iterations used a legacy namespaced key and a model_version field. This proposal standardizes the cleaned-up shape now used by Lesser:

"},{"location":"fep/c81b/#requirements","title":"Requirements","text":"

The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHOULD\", \"SHOULD NOT\", and \"MAY\" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

"},{"location":"fep/c81b/#vocabulary","title":"Vocabulary","text":""},{"location":"fep/c81b/#namespace","title":"Namespace","text":"

The namespace for this extension is:

https://spec.lessersoul.ai/ns/agent-attribution/v1#

Implementations advertise the extension in the ActivityPub @context using:

{\n  \"@context\": {\n    \"lessersoul\": \"https://spec.lessersoul.ai/ns/agent-attribution/v1#\",\n    \"agentAttribution\": {\n      \"@id\": \"lessersoul:agentAttribution\",\n      \"@type\": \"@json\"\n    }\n  }\n}\n

The /v1 path is a stability commitment. Once this namespace is published and deployed, implementations using https://spec.lessersoul.ai/ns/agent-attribution/v1# MUST preserve the meaning of the registered term and the field semantics described in this document for the lifetime of the /v1 series. Backward-incompatible changes MUST use a new versioned namespace.

"},{"location":"fep/c81b/#extension-property","title":"Extension property","text":"

agentAttribution

"},{"location":"fep/c81b/#fields","title":"Fields","text":"

trigger_type

trigger_details

memory_citations

delegated_by

delegated_by_did

scopes

constraints

schema_version

model_id

Informative note: this metadata extension is not a substitute for visible user-facing disclosure. Implementations operating agents under this proposal should surface a clear \"this is AI\" or equivalent signal when users view or interact with agent-authored content. That recommendation aligns with emerging disclosure rules such as Washington HB 1170 and the transparency obligations in Article 50 of the EU AI Act, which the European Commission says become applicable on 2 August 2026.

"},{"location":"fep/c81b/#json-ld-context-registration","title":"JSON-LD Context Registration","text":"

This proposal registers one extension term:

The value is typed as @json. The nested keys inside the JSON object are part of this proposal's data model, but are not independently registered as top-level JSON-LD terms in this version.

"},{"location":"fep/c81b/#examples","title":"Examples","text":""},{"location":"fep/c81b/#complete-note","title":"Complete Note","text":"
{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    {\n      \"lessersoul\": \"https://spec.lessersoul.ai/ns/agent-attribution/v1#\",\n      \"agentAttribution\": {\n        \"@id\": \"lessersoul:agentAttribution\",\n        \"@type\": \"@json\"\n      }\n    }\n  ],\n  \"id\": \"https://example.com/users/agent-alpha/statuses/123\",\n  \"type\": \"Note\",\n  \"attributedTo\": \"https://example.com/users/agent-alpha\",\n  \"content\": \"<p>Here is the scheduled digest.</p>\",\n  \"agentAttribution\": {\n    \"trigger_type\": \"scheduled\",\n    \"trigger_details\": \"daily digest job\",\n    \"memory_citations\": [\n      \"01JV7V8W9P8QJ5F48PHN6H2V7S\"\n    ],\n    \"delegated_by\": \"https://example.com/users/aron\",\n    \"delegated_by_did\": \"did:key:z6Mkexample\",\n    \"scopes\": [\n      \"read\",\n      \"write\",\n      \"follow\",\n      \"push\"\n    ],\n    \"constraints\": [\n      \"max_posts_per_hour:4\",\n      \"requires_approval\"\n    ],\n    \"schema_version\": \"1.0\",\n    \"model_id\": \"claude-3.7-sonnet\"\n  }\n}\n
"},{"location":"fep/c81b/#minimal-note","title":"Minimal Note","text":"
{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    {\n      \"lessersoul\": \"https://spec.lessersoul.ai/ns/agent-attribution/v1#\",\n      \"agentAttribution\": {\n        \"@id\": \"lessersoul:agentAttribution\",\n        \"@type\": \"@json\"\n      }\n    }\n  ],\n  \"id\": \"https://example.com/users/agent-alpha/statuses/124\",\n  \"type\": \"Note\",\n  \"attributedTo\": \"https://example.com/users/agent-alpha\",\n  \"content\": \"<p>I was asked to reply.</p>\",\n  \"agentAttribution\": {\n    \"trigger_type\": \"manual\"\n  }\n}\n
"},{"location":"fep/c81b/#relationship-to-other-standards","title":"Relationship to Other Standards","text":""},{"location":"fep/c81b/#fep-2677","title":"FEP-2677","text":"

FEP-2677 defines how to identify the Application Actor and distinguishes Application actors (triggered by internal application events) from Service actors (triggered by inbox activities or external events and behaving more like user-controlled accounts). Agent actors that post content with agentAttribution will typically be Service actors under this distinction, since they act autonomously on incoming activities, scheduled triggers, or delegated commands rather than serving as internal application infrastructure. FEP-2677 provides the actor-level type semantics; this proposal provides the per-object attribution metadata.

"},{"location":"fep/c81b/#fep-c390","title":"FEP-c390","text":"

FEP-c390 addresses identity proofs for ActivityPub actors. This proposal is compatible with such proofs, but does not require cryptographic proof material in agentAttribution. A future extension MAY define a proof-bearing variant.

"},{"location":"fep/c81b/#scim-agent-extension","title":"SCIM Agent Extension","text":"

SCIM agent-oriented schemas commonly model owners, entitlements, and agent capabilities. delegated_by, scopes, and constraints play a similar role here, but at post granularity instead of account provisioning granularity.

"},{"location":"fep/c81b/#gnap-delegation-semantics","title":"GNAP delegation semantics","text":"

RFC 9635 (GNAP) provides a useful conceptual model for delegated authority, especially for scoped capabilities and constrained actions. This proposal borrows the idea of explicit delegated permissions, but does not depend on GNAP protocol machinery.

"},{"location":"fep/c81b/#mastodon-attributiondomains-and-fediversecreator","title":"Mastodon attributionDomains and fediverse:creator","text":"

Existing fediverse attribution mechanisms demonstrate deployment precedent for publishing creator-related metadata on federated objects. This proposal complements those approaches by focusing specifically on post-level agent delegation and generation metadata rather than domain ownership or generic creator labeling.

"},{"location":"fep/c81b/#w3c-social-web-wg-timeline","title":"W3C Social Web WG timeline","text":"

Informative note: the proposed W3C Social Web Working Group charter published in November 2025 lists ActivityPub maintenance work with an expected completion target of Q3 2026. Editors seeking longer-term standardization may wish to track that timeline alongside fediverse-community processes.

"},{"location":"fep/c81b/#security-considerations","title":"Security Considerations","text":""},{"location":"fep/c81b/#references","title":"References","text":""},{"location":"fep/c81b/#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/c893/","title":"FEP-c893: DOAP","text":"Authors Status Date received Tracking issue Discussions Repository AvidSeeker avidseeker7@protonmail.com DRAFT 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.

"},{"location":"fep/c893/#example-doapjsonld-file","title":"Example 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":""},{"location":"fep/c893/#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/cb76/","title":"FEP-cb76: Content Addressed Vocabulary","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository a a@trwnh.com WITHDRAWN 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.

"},{"location":"fep/cb76/#example-1-specifying-a-new-extension-term","title":"Example 1: Specifying a new extension term","text":"

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.

"},{"location":"fep/cb76/#example-2-standardizing-an-fep-with-a-new-extension-term","title":"Example 2: Standardizing an FEP with a new extension term","text":"

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":""},{"location":"fep/cb76/#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/cd47/","title":"FEP-cd47: Federation-friendly Addressing and Deduplication Use-Cases","text":"Authors Status Date received Tracking issue Discussions Repository Bumblefudge bumblefudge@learningproof.xyz DRAFT 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:

  1. Actors are what ActivityPub calls Actors: fediverse \"accounts\", bots, etc.
  2. Behavior can here be limited to \"the Greater Activity Streams,\" meaning protocol-native activities wrapped in AS objects defined in AS, AP, or in a FEP with a @Context so as to be fully compatible with the protocol in a ffpr, still canonicalizable as RDF.
  3. Content can here be a catch-all for three different sub-types of data: the \"contents\" of an Activity with arbitrary/open-ended fields (like the user-generated content of a 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":"
  1. Abusive activity often evades or actor-based attribution and moderation; one common form of this on social media is copy-pasted allegations or rumors which bad actors deploy across throwaway accounts, bots, parrot-accounts, friends/accomplices, etc. For this reason, circulating content of an activity like a Note might need to be tracked independent of URI/location, server, and actor in a uniform way, rather than per-activity or per-property (the prevalence of copy-pasta might even be used as a health or authenticity metric for networks, although of course this should not be overapplied as it might return false positives for some of the funniest copypasta meta-memes of the Terminally Online)
  2. Hate-Speech and interpersonal Trust and safety issues- Relying on the moderation of a given server or service-provider benefits greatly from interoperable logs of moderation decisions; per-decision receipts or events might benefit from indirection or multi-addressing actors, behaviors, and/or content that moves or is deleted, as well as potentially cacheing it privately for later replay/re-assesment/etc.
  3. Being able to sort moderation by categories or topics enables selective reliance or inheritance of moderation (and re-moderation in categories where servers differ on policy or obligations)
  4. Some regulations require reporting and/or storing for a period of time toxic or offending content, even if the Actor is removed from the service or deletes their offending Behavior or Content-- a location-independent key/value store by content-address can sever the \"server's copy\" from the \"user's/published copy\" for these purposes.
  5. Inauthentic activity, whether it be \"synthetic users\" (AI impersonating users for whatever commercial or analytics-inflating purposes) or AI flotsam or deliberate and targetted impersonation, deliberately exploits weaknesses or good-faith in the actor system. Being able to attribute many actors or \"sybils\" to a common \"shadow actor\" (a sybil factory, a script, a coordination point of a bot army, etc.) often requires identifying the behavioral pattern or network traffic properties, even probabilistically and then mapping that back to actors that do not otherwise share location-addressing or domain provenance.
  6. \"Chain-Letter\" Behaviors and Content is a common pattern in end-to-end encrypted systems like WhatsApp, which has been grappling with the ethical, legal, and political/disinformation consecuences of \"chain-letter\" style viral communications for decades. To mitigate such messaging patterns without violating the privacy assumptions of users of E2EE systems, traffic is analyzed to mark outbox content identical to content in the same user's inbox so that future recipients see it marked as a \"forward\", and \"replication speed limits\" are imposed to prevent sensational disinformation or deliberately inauthentic replication moving faster than good information.
  7. Deduplication for thorough Copyright Takedowns: Some copyright regulations (if not in the letter of the regulation, at least in the best-practice or spirit of the law) put the burden of deduplication on a service provider, hoster, and/or aggregator of user-generated content, i.e., a copyright takedown does not apply to a specific URL or a specific file but to the copyrighted material (film, book, etc) that you are hosting and aggregating, across multiple users or new accounts a user might create, and even if others users download and re-uploads it in a slightly altered form. For this reason, many copyright-enforcement regimes today are highly centralized because they require both a mammoth data lake of all reported violations, and some kind of matching algorithm that deduplicates in media-specific and encoding-specific ways across slight edits, crops, tints, tinges, etc.
  8. Note: One way of doing this is a \"distance hash\" or \"locality-sensitive hashing\" to create hashes that index commonalities between inputs (patterns, segments in common, structures) or, in audio-visual media, \"perceptual hashing\" that tries to canonicalize the final visual output of a visual encoding rather than its underlying data structure, compression, codecs, etc. One open-source example of this is ISCC
"},{"location":"fep/cd47/#user-stories","title":"User Stories","text":"
  1. As a server offering data migration to users bringing over old data, I can ingest content-addressed versions of each all actor collections, behavior and content without having to know server-specific paths, id patterns, annotations, bucket-storage URL schemes, etc.
  2. I can also check imported datasets of behavior and/or content against content-addressed moderation records from the server that originally hosted them, assuming that server is still live and federated with me.
  3. I can also access a mirror or snapshot of that data, if the server is no longer online or disfederated from me.
  4. As a server (A) auditing another server (B)'s moderation track record, I can parse receipts or logs of moderation actions that refer to behaviors and content by addresses internal to Server B (not necessarily content-addressed!) and use those address to retrieve the behavior and/or content moderated, even if the actors, behaviors, and/or content in question have been deleted. (Note: this use case assumes Server B is authorized appropriately by Server A and assumes significant trust between servers.)
  5. As a server promising its users the \"right to be forgotten\" (or just honoring UX expectations of deletion), I can request receipts of deletion from the servers of followers and followers' followers' by content-address of the behavior or content being deleted. (See fediverse-ideas#55 on fediverse-ideas reference; may be more pertinent for attachments than activities per se)
"},{"location":"fep/cd47/#open-questions","title":"Open Questions","text":"
  1. Ephemeral, \"in-memory\" and/or service-internal objects (which the AP spec recommends to be excluded from the axiom that all Activity objects should have a unique and dereferenceable 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.
  2. Should the Sub-Resource Integrity hashes of @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?
  3. Do Actors, Behaviors, and/or Contents need to be RDF-canonicalized, or is JCS more appropriate for portability between JSON-LD native and JSON-only implementations? What translation corner-cases arise from assuming one or the other canonicalization?
  4. Thanks to @pukkamustard for sharing their IETF RFC draft for RDF/CBOR, explainer, and relevant prior art-- seems a well-designed and standardizable way forward, at least for Behaviors, particularly for implementations that already have RDF-canonicalization in the dependency graph and featureset.
  5. Similarly, how are attachments, links, images, videos, etc canonicalized for hashing in content-type-aware ways? How to deal with filesystems, bucket storage, etc? How are arbitrary files (uploads?) canonicalized?
  6. Is there a property for Actor objects to backlink to previous ids? How to deduplicate across migrations that change id property?
"},{"location":"fep/cd47/#references","title":"References","text":"

Normative

Informational

"},{"location":"fep/cd47/#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/d36d/","title":"FEP-d36d: Sharing Content Across Federated Forums","text":"Authors Status Date received Tracking issue Discussions Repository Zack Dunn zack@tilde.team DRAFT 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.

"},{"location":"fep/d36d/#history","title":"History","text":"

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.

"},{"location":"fep/d36d/#activity-handling","title":"Activity Handling","text":"

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.net FINAL 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:

"},{"location":"fep/d556/#discovery","title":"Discovery","text":"

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:

"},{"location":"fep/d556/#mastodon-example","title":"Mastodon Example","text":"

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:

Since no user-related actor link is provided for the server resource, the self rel value can be used without ambiguity

"},{"location":"fep/d556/#related-proposals","title":"Related Proposals","text":"

FEP-2677 suggests using NodeInfo for a similar purpose. There are several disadvantages of this compared to using WebFinger.

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":""},{"location":"fep/d556/#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/d767/","title":"FEP-d767: Extend ActivityPub with Valueflows","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Lynn Foster lynnfoster@social.coop WITHDRAWN 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":""},{"location":"fep/d767/#references","title":"References","text":""},{"location":"fep/d767/#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/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.org DRAFT 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:

"},{"location":"fep/d8c2/#discovery","title":"Discovery","text":"

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.

"},{"location":"fep/d8c2/#authorization-server-metadata","title":"Authorization Server Metadata","text":"

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.

"},{"location":"fep/d8c2/#context-document","title":"Context document","text":"

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.

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.

"},{"location":"fep/d8c2/#objectidasclientid","title":"objectIDAsClientID","text":"

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.

"},{"location":"fep/d8c2/#examples","title":"Examples","text":""},{"location":"fep/d8c2/#actor-flag","title":"Actor flag","text":"

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.

"},{"location":"fep/d8c2/#security-considerations","title":"Security considerations","text":""},{"location":"fep/d8c2/#iana-considerations","title":"IANA Considerations","text":""},{"location":"fep/d8c2/#oauth-authorization-server-metadata-registry","title":"OAuth Authorization Server Metadata Registry","text":"

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.

"},{"location":"fep/d8c2/#references","title":"References","text":""},{"location":"fep/d8c2/#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/d8c8/","title":"FEP-d8c8: BitTorrent Torrent 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.

"},{"location":"fep/d8c8/#background","title":"Background","text":"

A torrent file1 is an abbreviated, verifiable description of a file or directory that, at minimum, contains

A 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.

"},{"location":"fep/d8c8/#spec","title":"Spec","text":"

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.

"},{"location":"fep/d8c8/#json-encoding","title":"JSON Encoding","text":"

Torrent objects MUST be JSON encoded, mapping bencoded strings, integers, lists, and dictionaries to their counterparts in JSON.

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

"},{"location":"fep/d8c8/#bencoding","title":"Bencoding","text":"

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.

"},{"location":"fep/d8c8/#additional-keys","title":"Additional Keys","text":"

Torrent objects MUST contain their infohashes using the following keys, depending on whether they are v1, v2, or hybrid torrents:

Software 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:

"},{"location":"fep/d8c8/#abbreviated-representation","title":"Abbreviated Representation","text":"

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.

"},{"location":"fep/d8c8/#examples","title":"Examples","text":""},{"location":"fep/d8c8/#torrent-objects","title":"Torrent Objects","text":"

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):

"},{"location":"fep/d8c8/#v1","title":"v1","text":"Expand/collapse v1 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\": 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)

"},{"location":"fep/d8c8/#discussion","title":"Discussion","text":""},{"location":"fep/d8c8/#string-encoding","title":"String Encoding","text":"

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:

"},{"location":"fep/d8c8/#references","title":"References","text":""},{"location":"fep/d8c8/#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/d8c8/#footnotes","title":"Footnotes","text":"
  1. A .torrent file is often just called \"a torrent,\" as it is in this document.\u00a0\u21a9

  2. 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

"},{"location":"fep/d8c8/implementation/","title":"FEP d8c8 Reference Implementation","text":""},{"location":"fep/d8c8/implementation/#usage","title":"Usage","text":"

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,

"},{"location":"fep/d8c8/implementation/#see-also","title":"See Also","text":"

torrent-models - a more complete implementation of .torrent files in python

"},{"location":"fep/d9ad/","title":"FEP-d9ad: Create Conformance Tests for Fediverse Enhancement Proposals","text":"Authors Status Date received Tracking issue Discussions Repository bengo ben@bengo.co 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":""},{"location":"fep/d9ad/#context","title":"Context","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:

"},{"location":"fep/d9ad/#proposal","title":"Proposal","text":"

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

"},{"location":"fep/d9ad/#conformance-test-components","title":"Conformance Test Components","text":"

Each of these components may be a part of a conformance test.

"},{"location":"fep/d9ad/#applicability","title":"Applicability","text":"

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

"},{"location":"fep/d9ad/#example","title":"Example","text":"

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.

"},{"location":"fep/d9ad/#uri","title":"URI","text":"

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

"},{"location":"fep/d9ad/#example_1","title":"Example","text":"

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

"},{"location":"fep/d9ad/#uri_3","title":"URI","text":"

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

"},{"location":"fep/d9ad/#identifier","title":"Identifier","text":"

a globally unique identifier that identifies the Conformance Test

The Identifier MUST be a URI.

Inspired by

"},{"location":"fep/d9ad/#examples","title":"Examples","text":""},{"location":"fep/d9ad/#uri_4","title":"URI","text":"

https://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:

The 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

"},{"location":"fep/d9ad/#uri_6","title":"URI","text":"

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

"},{"location":"fep/d9ad/#example-name","title":"Example Name","text":"

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

"},{"location":"fep/d9ad/#uri_9","title":"URI","text":"

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

"},{"location":"fep/d9ad/#uri_10","title":"URI","text":"

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

"},{"location":"fep/d9ad/#uri_11","title":"URI","text":"

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

"},{"location":"fep/d9ad/#test-suite-memberships","title":"Test Suite Memberships","text":"

The test suites that the test is a part of, if any.

For each test suite membership, the test should specify:

"},{"location":"fep/d9ad/#uri_12","title":"URI","text":"

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

"},{"location":"fep/d9ad/#appendix-conformance","title":"Appendix: Conformance","text":""},{"location":"fep/d9ad/#what-is-conformance","title":"What is Conformance?","text":"

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":"FEP-db0e: Authentication mechanism for non-public groups","text":"Authors Status Date received Tracking issue Discussions Repository Gregory Klyushnikov activitypub@grishka.me DRAFT 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.

"},{"location":"fep/db0e/#using-actor-tokens","title":"Using actor tokens","text":"

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:

"},{"location":"fep/db0e/#the-smactortoken-endpoint","title":"The sm: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.

"},{"location":"fep/db0e/#making-use-of-the-actor-token","title":"Making use of the actor token","text":"

To use an actor token when fetching an object, pass it as Authorization: ActivityPubActorToken {...} HTTP header.

"},{"location":"fep/db0e/#generation-of-the-source-string-for-signature","title":"Generation of the source string for signature","text":"
  1. Iterate over the keys in the actor token JSON object, skipping signature, and transform them into the format key: value. Add these strings to an array.
  2. Sort the resulting array lexicographically.
  3. Join the strings with newline character (\\n, U+000A).
  4. Convert the resulting string to a UTF-8 byte array.
"},{"location":"fep/db0e/#generation-of-the-actor-token","title":"Generation of the actor token","text":"
  1. Verify that the requesting actor, as per HTTP signature, has access to the group (there are members with the same domain). If it does not, you MUST return a 403 error and stop.
  2. Create a JSON object with the fields above (except signature). It is RECOMMENDED that the validity period is 30 minutes, and it MUST NOT exceed 2 hours.
  3. Generate a signature source string as above, sign it, and wrap the signature into an object with signature, algorithm, and keyId fields.
  4. Add the object as a single element in the signatures array.
  5. Return the resulting JSON object to the client.
"},{"location":"fep/db0e/#verification-of-the-actor-token","title":"Verification of the actor token","text":"
  1. Check that the HTTP signature is valid, and that actor in the token object matches the actor ID from keyId in the HTTP signature. Otherwise, you MUST return a 403 and stop.
  2. In the 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.
  3. Check the validity time: 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.
  4. Generate the signature source string as above and verify the signature.
  5. Check that the object the requester is accessing is, in fact, part of a collection owned by issuer.
  6. If all of the above checks pass, return the requested object. Otherwise, return a 403.
"},{"location":"fep/db0e/#example-of-an-actor-token-object","title":"Example of an actor token object","text":"
{\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":""},{"location":"fep/db0e/#references","title":"References","text":""},{"location":"fep/db0e/#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/db70/","title":"FEP-db70: RemoveAll Collection Activity","text":"Authors Status Date received Tracking issue Discussions Repository Fred Hauschel @naturzukunft2026@mastodon.social DRAFT 2026-03-15 #784 Discussions codeberg"},{"location":"fep/db70/#summary","title":"Summary","text":"

This FEP defines a RemoveAll activity for batch-removing items from an ActivityPub collection. While the ActivityPub specification defines Remove for removing items from a collection, it requires the client to know the identity of every item to remove. RemoveAll fills this gap. It supports an optional FEP-34c1 filter to selectively remove items matching specific criteria (e.g. by type, by date, or by actor). Without a filter, all items are removed. RemoveAll is a generic collection operation \u2014 it can be used with any collection type, not just inboxes or notification collections.

"},{"location":"fep/db70/#motivation","title":"Motivation","text":"

ActivityPub's Remove activity requires the client to know the identity of every object to remove from a target collection. For collections that can grow large, clients need a way to batch-remove items without sending individual Remove activities for each item. Common use cases include:

Without a batch operation, a client must first fetch all items, then send individual Remove activities \u2014 which is both slow and prone to race conditions.

"},{"location":"fep/db70/#specification","title":"Specification","text":""},{"location":"fep/db70/#1-the-removeall-activity","title":"1. The RemoveAll Activity","text":"

RemoveAll is a new activity type that removes multiple items from a target collection.

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/db70\"\n  ],\n  \"type\": \"RemoveAll\",\n  \"actor\": \"https://example.com/actors/bob\",\n  \"target\": \"https://example.com/actors/bob/notifications\"\n}\n
"},{"location":"fep/db70/#properties","title":"Properties","text":"Property Type Required Description type String MUST RemoveAll actor IRI MUST The actor performing the operation target IRI MUST The collection to remove items from filter FilterRequest MAY A FEP-34c1 filter to select which items to remove

Without a filter, the server MUST remove all items from the target collection.

"},{"location":"fep/db70/#2-filtered-removeall-with-fep-34c1","title":"2. Filtered RemoveAll (with FEP-34c1)","text":"

When a filter property is present, only items matching the filter are removed. The filter uses the FEP-34c1 FilterRequest format with TREE relations.

Remove all items matching a specific property value:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/db70\",\n    \"https://w3id.org/fep/34c1\",\n    \"https://w3id.org/tree\"\n  ],\n  \"type\": \"RemoveAll\",\n  \"actor\": \"https://example.com/actors/bob\",\n  \"target\": \"https://example.com/actors/bob/some-collection\",\n  \"filter\": {\n    \"type\": \"FilterRequest\",\n    \"relation\": [\n      {\n        \"type\": \"EqualToRelation\",\n        \"path\": { \"@id\": \"rdf:type\" },\n        \"value\": { \"@id\": \"as:Create\" }\n      }\n    ]\n  }\n}\n

Remove all items older than a given date:

{\n  \"type\": \"RemoveAll\",\n  \"actor\": \"https://example.com/actors/bob\",\n  \"target\": \"https://example.com/actors/bob/some-collection\",\n  \"filter\": {\n    \"type\": \"FilterRequest\",\n    \"relation\": [\n      {\n        \"type\": \"LessThanRelation\",\n        \"path\": { \"@id\": \"as:published\" },\n        \"value\": { \"@value\": \"2026-02-17T00:00:00Z\", \"@type\": \"xsd:dateTime\" }\n      }\n    ]\n  }\n}\n

Combine multiple filter criteria (AND):

{\n  \"type\": \"RemoveAll\",\n  \"actor\": \"https://example.com/actors/bob\",\n  \"target\": \"https://example.com/actors/bob/some-collection\",\n  \"filter\": {\n    \"type\": \"FilterRequest\",\n    \"relation\": [\n      {\n        \"type\": \"EqualToRelation\",\n        \"path\": { \"@id\": \"rdf:type\" },\n        \"value\": { \"@id\": \"as:Create\" }\n      },\n      {\n        \"type\": \"LessThanRelation\",\n        \"path\": { \"@id\": \"as:published\" },\n        \"value\": { \"@value\": \"2026-02-17T00:00:00Z\", \"@type\": \"xsd:dateTime\" }\n      }\n    ]\n  }\n}\n

Multiple relation entries are combined with AND \u2014 all criteria must match for an item to be removed.

"},{"location":"fep/db70/#3-server-behavior","title":"3. Server Behavior","text":""},{"location":"fep/db70/#4-response","title":"4. Response","text":"

The server SHOULD respond with 200 OK and the number of removed items:

{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"summary\": \"Removed 12 items from the collection\"\n}\n
"},{"location":"fep/db70/#security-considerations","title":"Security Considerations","text":""},{"location":"fep/db70/#conformance","title":"Conformance","text":"

A conforming server MUST: - Process RemoveAll activities targeting a collection owned by the actor - Remove all items when no filter is present - Remove only matching items when a FEP-34c1 filter is present

A conforming server SHOULD: - Support FEP-34c1 FilterRequest in the filter property

"},{"location":"fep/db70/#implementations","title":"Implementations","text":""},{"location":"fep/db70/#references","title":"References","text":""},{"location":"fep/db70/#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/dc88/","title":"FEP-dc88: Formatting Mathematics","text":"Authors Status Date received Tracking issue Discussions Repository Calvin Lee pounce@integraldoma.in DRAFT 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.

"},{"location":"fep/dc88/#sanitizing-mathematically-formatted-text","title":"Sanitizing Mathematically Formatted Text","text":"

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.

"},{"location":"fep/dc88/#examples","title":"Examples","text":"
{\"@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.

"},{"location":"fep/dc88/#semantic-attributes","title":"Semantic Attributes","text":"Element Attributes Values all 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":""},{"location":"fep/dc88/#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/dd4b/","title":"FEP-dd4b: Quote Posts","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.org DRAFT 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.

"},{"location":"fep/dd4b/#specification","title":"Specification","text":""},{"location":"fep/dd4b/#examples","title":"Examples","text":""},{"location":"fep/dd4b/#basic-quote-post","title":"Basic Quote Post","text":"

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.

"},{"location":"fep/dd4b/#security-considerations","title":"Security considerations","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.

"},{"location":"fep/dd4b/#privacy-considerations","title":"Privacy considerations","text":""},{"location":"fep/dd4b/#consent-to-be-shared","title":"Consent to be shared","text":"

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":""},{"location":"fep/dd4b/#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/e229/","title":"FEP-e229: Best practices for extensibility","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 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\"].

"},{"location":"fep/e229/#ignore-json-ld-context-if-you-dont-understand-it","title":"Ignore JSON-LD context if you don't understand it","text":"

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.

"},{"location":"fep/e229/#ld-aware-consumers","title":"LD-aware consumers","text":""},{"location":"fep/e229/#assume-the-as2-context-if-none-is-provided","title":"Assume the AS2 context if none is provided","text":"

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.

"},{"location":"fep/e229/#ld-unaware-producers","title":"LD-unaware producers","text":""},{"location":"fep/e229/#declare-iris-for-terms-that-are-expected-to-be-shared","title":"Declare IRIs for terms that are expected to be shared","text":"

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.

"},{"location":"fep/e229/#ld-aware-producers","title":"LD-aware producers","text":""},{"location":"fep/e229/#avoid-unnecessary-term-prefixes","title":"Avoid unnecessary term prefixes","text":"

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\".

"},{"location":"fep/e229/#consider-producing-documents-compacted-against-only-the-as2-context-document","title":"Consider producing documents compacted against only the AS2 context document","text":"

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:

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":""},{"location":"fep/e229/#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/e232/","title":"FEP-e232: Object Links","text":"Authors Status Date received Date final Tracking issue Discussions Repository silverpill @silverpill@mitra.social FINAL 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.

"},{"location":"fep/e232/#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/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:

"},{"location":"fep/e232/#examples","title":"Examples","text":"

(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.

"},{"location":"fep/e232/#implementations","title":"Implementations","text":""},{"location":"fep/e232/#references","title":"References","text":""},{"location":"fep/e232/#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/e3e9/","title":"FEP-e3e9: Actor-Relative URLs","text":"Authors Status Date received Tracking issue Discussions Repository Dmitri Zagidulin @dmitri@social.coop, bumblefudge bumblefudge@learningproof.xyz DRAFT 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:

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:

In addition:

"},{"location":"fep/e3e9/#validating-an-objects-authorcontroller","title":"Validating an Object's Author/Controller","text":"

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:

  1. The Client performs an HTTP GET request on the Object or Collection, as usual, including any currently required authorization headers.
  2. The client performing the 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.
  3. The Client follows the redirect and automatically fetches the object specified in the Location header of the 302 response (this behavior is the default in most HTTP clients).
  4. The Client extracts the current URL of the Object. This is the URL specified in the 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.
  5. The Client retrieves the Actor profile corresponding to this Object's author/ controller (the actor or attributedTo property).
  6. 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.

  7. 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.

"},{"location":"fep/e3e9/#client-side-implementation","title":"Client-Side Implementation","text":"

An ActivityPub client conforming to this FEP:

  1. When encountering an Actor-Relative URL as an ID of an object, fetch it using the same HTTP GET mechanism that it currently does.
  2. Note: An Actor-Relative URL is defined as a URL containing the service and relativeRef query parameters.
  3. The client MUST follow the 302 redirect in the response.
  4. The client MUST perform the validation steps outlined in the Validating an Object's Author/Controller section above.
"},{"location":"fep/e3e9/#server-side-implementation","title":"Server-Side Implementation","text":"

On the server side (specifically, the server hosting the Actor profile), an ActivityPub server conforming to this FEP:

  1. For every request to the Actor profile object (for example, to 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).
  2. 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:

  3. If the Actor profile does not contain a top level service property: INVALID

  4. If the Actor has a service property, but its value is null or []: INVALID
  5. Search 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

  6. 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).

  7. 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.

"},{"location":"fep/e3e9/#example-server-side-request-and-response","title":"Example Server-Side Request and Response","text":"

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:

  1. The direct URL (also called current location URL), https://old-storage-provider.example/AP/objects/567
  2. The indirect Actor-Relative URL https://alice-personal-site.example/actor?service=storage&relativeRef=/AP/objects/567

When 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.

"},{"location":"fep/e3e9/#references","title":"References","text":""},{"location":"fep/e3e9/#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/e965/","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.coop DRAFT 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.

"},{"location":"fep/e965/#current-approaches","title":"Current Approaches","text":"

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).

"},{"location":"fep/e965/#specification","title":"Specification","text":""},{"location":"fep/e965/#conformance","title":"Conformance","text":"

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:

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.

"},{"location":"fep/e965/#announcing-a-migration-or-deactivation-event","title":"Announcing a Migration or Deactivation Event","text":"

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:

"},{"location":"fep/e965/#behavior-for-unfamiliar-actor-uris","title":"Behavior for unfamiliar Actor URIs","text":"

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:

"},{"location":"fep/e965/#interpreting-a-move-activity-or-an-announce-activity-of-a-deactivated-actor","title":"Interpreting a Move Activity or an Announce Activity of a Deactivated Actor","text":"

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.

"},{"location":"fep/e965/#open-issues","title":"Open Issues","text":"
  1. Are there others to which an Actor-update Move or Announce should be addressed beyond just the old Actor's Followers, somehow imported? is it worth calling out server-instance Actors, since they might also want to know for... idunno moderation reasons?
  2. Announce Activity example
  3. Address Actor Equivalence Attestation objects explicitly, or leave up to implementer imagination?
"},{"location":"fep/e965/#references","title":"References","text":""},{"location":"fep/e965/#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/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:

  1. deactivation, OR
  2. migration to another URI, OR
  3. duplication at another URI.
"},{"location":"fep/e965/fep-e965-test-case/#about-this-test","title":"About this Test","text":"

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:

"},{"location":"fep/e965/fep-e965-test-case/#applicability","title":"Applicability","text":"

This test applies directly to the actor input.

"},{"location":"fep/e965/fep-e965-test-case/#test-targets","title":"Test Targets","text":""},{"location":"fep/e965/fep-e965-test-case/#expectations","title":"Expectations","text":"
  1. movedTo - MUST be a URI OR an empty string, if present
  2. copiedTo - MUST be a URI, if present
  3. movedTo and copiedTo MUST NOT both be present
  4. actor JSON's @context array SHOULD include \"https://w3id.org/fep/7628\" to signal conformance
"},{"location":"fep/e965/fep-e965-test-case/#assumptions","title":"Assumptions","text":""},{"location":"fep/e965/fep-e965-test-case/#1-how-to-determine-whether-an-actor-object-supports-fep-7628-semantics","title":"1. How to Determine Whether an Actor object supports FEP-7628 Semantics","text":"

For the purposes of determining whether the active status and migration history of a given Actor can be tested by this test case:

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:

"},{"location":"fep/e965/fep-e965-test-case/#property-value-evaluation-logic-pseudocode","title":"Property Value evaluation logic (Pseudocode)","text":""},{"location":"fep/e965/fep-e965-test-case/#test-cases","title":"Test Cases","text":"

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/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

"},{"location":"fep/e965/fep-e965-test-case/#both-movedto-and-copiedto-present","title":"both movedTo 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

"},{"location":"fep/e965/fep-e965-test-case/#movedto-set-to-array","title":"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

"},{"location":"fep/e965/fep-e965-test-case/#copiedto-contains-invalid-uri","title":"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

"},{"location":"fep/e965/fep-e965-test-case/#movedto-set-to-invalid-uri-1","title":"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

"},{"location":"fep/e965/fep-e965-test-case/#movedto-set-to-invalid-uri-2","title":"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

"},{"location":"fep/e965/fep-e965-test-case/#valid-deactivated-actor","title":"Valid Deactivated Actor","text":"

input

{\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

"},{"location":"fep/e965/fep-e965-test-case/#valid-migrated-actor","title":"Valid Migrated Actor","text":"

input

{\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

"},{"location":"fep/e965/fep-e965-test-case/#valid-migrated-actor-missing-tombstone","title":"Valid Migrated Actor (Missing Tombstone)","text":"

This 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

{\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

"},{"location":"fep/e965/fep-e965-test-case/#valid-multi-homed-actor","title":"Valid Multi-homed Actor","text":"

input

{\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

"},{"location":"fep/e965/fep-e965-test-case/#valid-multi-homed-actor-copiedto-set-to-array","title":"Valid Multi-homed Actor (copiedTo 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

"},{"location":"fep/e965/fep-e965-test-case/#glossary","title":"Glossary","text":""},{"location":"fep/e965/fep-e965-test-case/#outcome","title":"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:

"},{"location":"fep/e965/fep-e965-test-case/#requirements-mapping","title":"Requirements Mapping","text":""},{"location":"fep/e965/fep-e965-test-case/#references","title":"References","text":""},{"location":"fep/e965/fep-e965-test-case/#change-log","title":"Change Log","text":""},{"location":"fep/e965/fep-e965-test-case/#issues-list","title":"Issues List","text":""},{"location":"fep/eb22/","title":"FEP-eb22: Supported ActivityStreams types with NodeInfo","text":"Authors Status Date received Tracking issue Discussions Repository Manton Reece manton@micro.blog DRAFT 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:

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.

"},{"location":"fep/eb22/#supporting-common-social-features-and-book-reviews-review","title":"Supporting common social features and book reviews (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:

"},{"location":"fep/eb22/#references","title":"References","text":""},{"location":"fep/eb22/#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/eb48/","title":"FEP-eb48: Hashtags","text":"Authors Status Date received Tracking issue Discussions Repository AvidSeeker avidseeker7@protonmail.com DRAFT 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:

  1. It starts with the # symbol.
  2. It is followed by one or more alphanumeric characters (letters A-Z, a-z, or digits 0-9).
  3. It may include underscores (_) but must not include any other special characters, spaces, or punctuation within the hashtag itself.
"},{"location":"fep/eb48/#examples","title":"Examples","text":"

The following examples illustrate how hashtags should be identified and highlighted:

"},{"location":"fep/eb48/#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/ee3a/","title":"FEP-ee3a: Exif metadata support","text":"Authors Status Date received Tracking issue Discussions Repository Marcin Czachurski mczachurski@icloud.com DRAFT 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.

"},{"location":"fep/ee3a/#motivation","title":"Motivation","text":"

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.

"},{"location":"fep/ee3a/#privacy-considerations","title":"Privacy considerations","text":"

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.

"},{"location":"fep/ee3a/#property-names","title":"Property names","text":"

Property names MUST be strings corresponding to the Exif specification Field name (e.g., \"FNumber\", \"PhotographicSensitivity\", \"FocalLength\").

"},{"location":"fep/ee3a/#recommended-fields","title":"Recommended fields","text":"

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.

Name (PropertyValue.name) Applies to Description 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/#implementations","title":"Implementations","text":""},{"location":"fep/ee3a/#servers","title":"Servers","text":"

This list is not comprehensive:

"},{"location":"fep/ee3a/#references","title":"References","text":""},{"location":"fep/ee3a/#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/ef61/","title":"FEP-ef61: Portable Objects","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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:

"},{"location":"fep/ef61/#history","title":"History","text":"

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

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.

"},{"location":"fep/ef61/#comparing-ap-uris","title":"Comparing 'ap' URIs","text":"

Two 'ap' URIs are equivalent when their canonical forms are identical.

To produce a canonical 'ap' URI, the following operations MUST be performed:

"},{"location":"fep/ef61/#did-methods","title":"DID methods","text":"

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/#portable-objects","title":"Portable objects","text":"

Example of a portable object:

{\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/#retrieving-objects","title":"Retrieving objects","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 an object is not public, the server MUST NOT serve it unless the request is signed by an actor who belongs to object's intended audience.

When working with portable objects, the server SHOULD treat 'ap' URIs as opaque identifiers (semantic routing).

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:

Important

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 identified by 'ap' URIs (including inbox and outbox collections) MAY be served without FEP-8b32 integrity proofs. Consuming implementations MUST NOT process unsecured collections attributed to a portable actor if they were retrieved from a server that is not listed in the gateways array of the actor document.

Portable collections can be filtered and paginated in a same way as non-portable collections. A gateway MUST remove the integrity proof when generating a view of a collection created by a FEP-ae97 client.

"},{"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.

"},{"location":"fep/ef61/#webfinger-addresses","title":"WebFinger addresses","text":"

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.

"},{"location":"fep/ef61/#discussion","title":"Discussion","text":"

(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:

"},{"location":"fep/ef61/#did-services","title":"DID services","text":"

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.

"},{"location":"fep/ef61/#media-access-control","title":"Media access control","text":"

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.

"},{"location":"fep/ef61/#compatibility_1","title":"Compatibility","text":"

The following alternatives to gateway-based compatible IDs are being considered:

  1. Use regular HTTP(S) URIs but specify the canonical 'ap' URI using the 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.
  2. Alter object ID depending on the capabilities of the peer (which can be reported by NodeInfo or some other mechanism).
"},{"location":"fep/ef61/#implementations","title":"Implementations","text":""},{"location":"fep/ef61/#references","title":"References","text":""},{"location":"fep/ef61/#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/efda/","title":"FEP-efda: Followable objects","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 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:

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.

"},{"location":"fep/efda/#requirements","title":"Requirements","text":"

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:

A proposed modified requirement and algorithm are as follows:

"},{"location":"fep/efda/#algorithm","title":"Algorithm","text":"

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":""},{"location":"fep/efda/#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/f011/","title":"FEP-f011: Full-Text Search Query Syntax for ActivityPub","text":"Authors Status Type Date received Tracking issue Discussions Repository Steve Bate svc-fep@stevebate.net DRAFT implementation 2026-03-17 #791 Discussions codeberg"},{"location":"fep/f011/#summary","title":"Summary","text":"

This proposal defines a full-text search syntax for ActivityPub client support.

The syntax supports:

"},{"location":"fep/f011/#terminology","title":"Terminology","text":"

The term query string means the value of the q query parameter in an HTTP request against a search endpoint.

The term faceted expression means <facet>:expr, where expr may be a term, phrase, regex, range, or parenthesized subexpression and the facet is typically a JSON property path.

The term unfaceted expression means a term, phrase, regex, or group that does not have a facet prefix.

"},{"location":"fep/f011/#query-syntax","title":"Query Syntax","text":""},{"location":"fep/f011/#grammar","title":"Grammar","text":"

The (simplified) grammar proposed by this FEP is:

query      := or_expr\nor_expr    := and_expr (\"OR\" and_expr)*\nand_expr   := unary ((\"AND\" unary) | (implicit_and unary))*\nunary      := \"NOT\" unary | primary\nprimary    := term | phrase | regex | range | field_expr | \"(\" query \")\"\nfield_expr := WORD \":\" (primary | \"(\" query \")\")\nrange      := (\"[\" | \"{\") bound TO bound (\"]\" | \"}\")\nbound      := WORD | phrase\nphrase     := '\"' <any-char-except-quote>* '\"'\nregex      := \"/\" <regex-body> \"/\"\nterm       := WORD\n

An AND is implicit when two primaries are adjacent. The \"regex-body\" SHOULD conform to RFC9485 (Interoperable Regular Expression Format).

A full ABNF grammar is available. This can be used with parser generators like ANTLR.

"},{"location":"fep/f011/#token-rules","title":"Token Rules","text":""},{"location":"fep/f011/#operator-precedence","title":"Operator Precedence","text":"

The parser evaluates operators in this order:

  1. Parenthesized expressions
  2. Unary NOT
  3. AND (explicit and implicit)
  4. OR

AND and OR are left-associative.

"},{"location":"fep/f011/#faceted-expressions","title":"Faceted Expressions","text":"

A fielded expression has this form:

<facet>:value\n<facet>:\"some phrase\"\n<facet>:/xy.*/\n<facet>:[10 TO 20]\n<facet>:(cats OR dogs)\n

<facet>:(...) applies the nested expression to the facet value.

"},{"location":"fep/f011/#range-expressions","title":"Range Expressions","text":"

Ranges support inclusive and exclusive delimiters independently:

Unbounded bounds may use * where supported by the evaluator implementation (e.g., rank:[4 TO *]).

"},{"location":"fep/f011/#search-endpoint-discovery","title":"Search Endpoint Discovery","text":"

The search endpoint is specified using the search property of the ActivityPub actor endpoints object. For example:

{\n  \"@context\": [\n    \"https://w3id.org/fep/f011\"\n    \"https://www.w3.org/ns/activitystreams\"\n  ],\n  ...\n  \"endpoints\": {\n    \"search\": \"https://server.example/search\"\n  }\n}\n
"},{"location":"fep/f011/#http-usage","title":"HTTP Usage","text":"

This syntax is used as the q parameter on the search endpoint.

GET https://server.example/search?q=<query>\n
"},{"location":"fep/f011/#responses","title":"Responses","text":"

Responses MUST be an ActivityPub Collection or OrderedCollection. Paging is optional. If an OrderedCollection is returned, the results SHOULD be considered ranked although the ranking criteria will not be known by the client ( possibilities are relevance/BM25, recency, etc.).

"},{"location":"fep/f011/#example-1-simple-primary-term","title":"Example 1: Simple Primary Term","text":"

Request: cats

GET /search?q=cats HTTP/1.1\nHost: social.example\nAccept: application/activity+json\n
"},{"location":"fep/f011/#example-2-faceted-unfaceted-phrase","title":"Example 2: Faceted + Unfaceted phrase","text":"

Request: tag:fediverse language:en \"ActivityPub client search\"

GET /search?q=tag%3Afediverse%20language%3Aen%20%22ActivityPub%20client%20search%22 HTTP/1.1\nHost: social.example\nAccept: application/activity+json\n

Response:

{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"type\": \"OrderedCollection\",\n  \"id\": \"https://social.example/search?q=tag:fediverse language:en \\\"ActivityPub client search\\\"\",\n  \"totalItems\": 1,\n  \"orderedItems\": [\n    {\n      \"id\": \"https://social.example/notes/1\",\n      \"type\": \"Note\"\n    }\n  ]\n}\n
"},{"location":"fep/f011/#example-3-boolean-grouping","title":"Example 3: Boolean + Grouping","text":"

Request:v(cats OR dogs) AND NOT testing

GET /search?q=%28cats%20OR%20dogs%29%20AND%20NOT%20testing HTTP/1.1\nHost: social.example\nAccept: application/activity+json\n
"},{"location":"fep/f011/#example-4-regex","title":"Example 4: Regex","text":"

Request: type:Person preferredUsername:/alic.*/

GET /search?q=type%3APerson%20preferredUsername%3A%2Ffedi.%2A%2F\nHost: social.example\nAccept: application/activity+json\n

This could be useful for auto completion. Using a type:HashTag search term could similarly be used for hashtag autocompletion.

"},{"location":"fep/f011/#example-5-range","title":"Example 5: Range","text":"

Request: rank:[2 TO 4]

GET /search?q=rank%3A%5B2%20TO%204%5D HTTP/1.1\nHost: social.example\nAccept: application/activity+json\n
"},{"location":"fep/f011/#error-handling","title":"Error Handling","text":"

Invalid syntax MUST result in an HTTP client error status. Examples include:

Implementations MAY include details in a problem response body.

"},{"location":"fep/f011/#interoperability-notes","title":"Interoperability Notes","text":""},{"location":"fep/f011/#implementations","title":"Implementations","text":""},{"location":"fep/f011/#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/f06f/","title":"FEP-f06f: Object observers","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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.

"},{"location":"fep/f06f/#non-forwarding-observers","title":"Non-forwarding observers","text":"

If forwarding is not desirable, object observers can use Announce activity to distribute observed activities.

"},{"location":"fep/f06f/#examples","title":"Examples","text":"

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":""},{"location":"fep/f06f/#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/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.nz DRAFT 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.

"},{"location":"fep/f15d/#referencing-threaded-objects-as-a-whole","title":"Referencing threaded objects as a whole","text":"

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:

"},{"location":"fep/f15d/#audience-wrapper","title":"Audience wrapper","text":"

Implementations MAY wrap the Move activity in an Announce activity. Receivers are recommended to support both types.

"},{"location":"fep/f15d/#removal","title":"Removal","text":"

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.

"},{"location":"fep/f15d/#moderator-collection-check","title":"Moderator Collection Check","text":"

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.

"},{"location":"fep/f15d/#implementors","title":"Implementors","text":""},{"location":"fep/f15d/#references","title":"References","text":""},{"location":"fep/f15d/#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/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.social FINAL 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:

"},{"location":"fep/f1d5/#implementations","title":"Implementations","text":""},{"location":"fep/f1d5/#servers","title":"Servers","text":"

This list is not comprehensive:

"},{"location":"fep/f1d5/#clients","title":"Clients","text":""},{"location":"fep/f1d5/#references","title":"References","text":""},{"location":"fep/f1d5/#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/f228/","title":"FEP-f228: Backfilling conversations","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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.

"},{"location":"fep/f228/#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/f228/#collection-of-posts","title":"Collection of posts","text":"

The items of this collection are attributed objects, such as Note or Article objects. It represents a thread, as seen 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:

It 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.

contextHistory property is used to make a reference from a top-level post to related collection of activities.

Note

The collection of conversation activities was originally a part of Conversation Containers proposal.

"},{"location":"fep/f228/#reading-collections","title":"Reading collections","text":"

After top-level post of a conversation is discovered, the whole conversation can be retrieved using the following algorithm:

"},{"location":"fep/f228/#implementations","title":"Implementations","text":"

Collection of posts:

Collection of activities:

"},{"location":"fep/f228/#references","title":"References","text":""},{"location":"fep/f228/#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/fb2a/","title":"FEP-fb2a: Actor metadata","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 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).

"},{"location":"fep/fb2a/#1-using-activitystreams-note-instead-of-schemaorg-propertyvalue","title":"1. Using ActivityStreams Note instead of schema.org PropertyValue","text":"

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.

"},{"location":"fep/fb2a/#3-defining-generic-actor-metadata-as-links","title":"3. Defining generic actor metadata as links","text":"

Actor metadata fields may also take the form of a link rather than a content value.

"},{"location":"fep/fb2a/#4-backwards-compatibility-with-legacy-implementations-of-profile-fields-using-incorrect-schemaorg-iris","title":"4. Backwards compatibility with legacy implementations of profile fields using incorrect schema.org IRIs","text":"

(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:

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).

"},{"location":"fep/fb2a/#5-examples-of-current-legacy-implementations-compared-to-serialization-according-to-this-fep","title":"5. Examples of current legacy implementations compared to serialization according to this FEP","text":"

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":""},{"location":"fep/fb2a/#references","title":"References","text":""},{"location":"fep/fb2a/#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/fc48/","title":"FEP-fc48: Generic ActivityPub server","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT implementation 2026-02-27 #769 Discussions codeberg"},{"location":"fep/fc48/#summary","title":"Summary","text":"

Generic ActivityPub server is a server that implements standard ActivityPub client API or FEP-ae97 client API, and can process any activity (including activities those behavior is not defined in the ActivityPub specification).

"},{"location":"fep/fc48/#motivation","title":"Motivation","text":"

Most of existing ActivityPub servers are designed for a specific type of application: a micro-blog, a forum, a video sharing service. This leads to a situation where users are expected to have separate accounts for each application type.

Even a server that implements the entirety of ActivityPub specification is limited because it doesn't support custom activities.

A truly generic server does not have these limitations and can work with any type of client application.

"},{"location":"fep/fc48/#object-classification","title":"Object classification","text":"

A generic server MUST determine the class of an object before processing it. The classification of ActivityPub objects is covered in FEP-2277: ActivityPub core types.

"},{"location":"fep/fc48/#activities-and-side-effects","title":"Activities and side effects","text":"

A generic server MUST support the following basic activities:

The side effects of these activities are implicit and are defined in the ActivityPub specification.

A generic server can only carry out the side effects of basic activities. Therefore, clients MUST specify the side effects of all other activities as additional activities. Clients can embed them into an activity using the result property, or send them separately.

A generic server MAY support other activity types defined in the ActivityPub specification.

"},{"location":"fep/fc48/#authentication-and-authorization","title":"Authentication and authorization","text":"

A generic server MUST verify all objects according to the universal authentication and authorization procedures described in FEP-fe34: Origin-based security model.

When processing activities submitted by a client, the server can only check permissions for basic activities. Therefore, recipients SHOULD NOT use the same-origin policy to verify permissions.

"},{"location":"fep/fc48/#managing-collections","title":"Managing collections","text":"

A generic server MUST automatically create inbox, outbox and other actor collections defined in the ActivityPub specification after registering an actor.

Other collections MUST be created by clients using Create activities where object is an empty collection.

"},{"location":"fep/fc48/#references","title":"References","text":""},{"location":"fep/fc48/#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/fe34/","title":"FEP-fe34: Origin-based security model","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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 responsible for enforcing security boundaries between the hosted actors. This assumption is based on the general consensus that served objects can be trusted and that their ownership doesn't require verification. 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 the server enforces boundaries between them.

"},{"location":"fep/fe34/#comparing-origins","title":"Comparing origins","text":"
  1. Let uri-scheme be the scheme component of the URI, converted to lowercase.
  2. Let uri-host be the host component of the URI, converted to lowercase.
  3. If there is no port component of the URI, let uri-port be the default port for the protocol given by uri-scheme. Otherwise, let uri-port be the port component of the URI.
  4. Return the triple (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.

"},{"location":"fep/fe34/#embedding","title":"Embedding","text":"

In some cases, an embedded object can be trusted when its wrapping object is trusted:

Servers MUST NOT allow clients to publish activities where embedded objects are owned by another local actor.

Embedded non-anonymous objects SHOULD NOT be partial representations. A server that relies on embedding for authentication might save a partial representation of an object to the cache, replacing the full object.

Note

Partial representations could be marked to prevent caching. Potential methods for achieving this are discussed in the issue https://codeberg.org/silverpill/feps/issues/21.

"},{"location":"fep/fe34/#authorization","title":"Authorization","text":"

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:

In some cases ownership might be implicit. Examples:

Anonymous objects are not supposed to have an owner.

Applications can use the following algorithm to determine the owner of an object:

  1. Run the duck typing algorithm specified in FEP-2277.
  2. If the type is Link, return error.
  3. If the type is neither Object nor Collection, and the object has an attributedTo property, return error.
  4. If the type is Actor, return the value of the id property.
  5. If the type is VerificationMethod, return the value of the controller property.
  6. If the type is PublicKey, return the value of the owner property.
  7. If the type is Activity, return the value of the actor property.
  8. If the type is 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.

"},{"location":"fep/fe34/#comparing-owners","title":"Comparing owners","text":"

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:

Servers MUST ensure that activities published by a client do not represent unauthorized actions. This includes activities embedded within other activities and objects.

"},{"location":"fep/fe34/#access-control","title":"Access control","text":"

The intended audience of an object is specified using the following properties: to, cc, bto, bcc and audience.

An object addressed to https://www.w3.org/ns/activitystreams#Public can be retrieved by anyone. When an object is not addressed to public, the server MUST verify that the GET request contains an HTTP signature created using a key whose owner belongs to the 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.

Note

Some servers always require signed requests in an attempt to limit access to public objects. However, this measure is often ineffective and can be circumvented by using a different domain name to serve the public key.

"},{"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:

"},{"location":"fep/fe34/#cross-origin-relationships","title":"Cross-origin relationships","text":"

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:

"},{"location":"fep/fe34/#references","title":"References","text":""},{"location":"fep/fe34/#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/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.

"},{"location":"fep/fffd/#4-merging","title":"4. Merging","text":"

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.

"},{"location":"fep/fffd/#5-protocols","title":"5. Protocols","text":"

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.

"},{"location":"fep/fffd/#51-well-known-alternate-protocols","title":"5.1. Well-known Alternate Protocols","text":""},{"location":"fep/fffd/#52-well-known-media-types","title":"5.2. Well-known Media Types","text":"

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":""},{"location":"fep/fffd/#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.

"}]} \ 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:

  1. Fork this repository, and then clone it to your local machine. Check the Codeberg Cheat sheet on how to prepare your Pull Request.
  2. Think of a title for the FEP you want to submit.
  3. Compute the identifier of the FEP by computing the hash of the title. This can be done with following Unix command:
$ echo -n \"The title of my proposal\" | sha256sum | cut -c-4\nb3f0\n
  1. Create a subdirectory of fep/ using the identifier you just computed.
  2. Copy the FEP template (fep-xxxx-template.md) to this subdirectory and change the filename appropriately.
  3. Use the identifer as the \"slug\" when filling out the frontmatter.

  4. Write down your idea in the newly created file and commit it to a new branch in your repository (ex. fep-xxxx).

  5. When you are ready to submit the FEP, change the value of dateReceived field in the frontmatter to the current date.
  6. Create a discussion topic for your FEP. You can use ActivityPub category on the SocialHub forum.
  7. Add discussionsTo field containing the URL of the discussion topic to the front matter of your FEP.
  8. Create a Pull Request to complete Step 1 of FEP-a4ed: The Fediverse Enhancement Proposal Process. Further process is described in FEP-a4ed.

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 Type Tracking issue Discussions Received FEP-8b32: Object Integrity Proofs Implementation 9 #29 Discussions 2022-11-12 FEP-c390: Identity Proofs Implementation 3 #34 Discussions 2022-11-23 FEP-fb2a: Actor metadata Informational #45 - 2022-12-09 FEP-c118: Content licensing support Informational #57 - 2023-01-16 FEP-7888: Demystifying the context property Informational #68 Discussions 2023-03-14 FEP-888d: Using https://w3id.org/fep as a base for FEP-specific namespaces Informational #83 Discussions 2023-04-10 FEP-0ea0: Payment Links Implementation #88 Discussions 2023-04-18 FEP-fffd: Proxy Objects Informational #95 - 2023-04-29 FEP-4adb: Dereferencing identifiers with webfinger Informational #100 - 2023-05-13 FEP-c648: Blocked Collection Informational #123 - 2023-06-14 FEP-bad1: Object history collection Informational #124 - 2023-06-15 FEP-4ccd: Pending Followers Collection and Pending Following Collection Informational #129 Discussions 2023-06-21 FEP-d36d: Sharing Content Across Federated Forums Informational #125 - 2023-07-01 FEP-1970: Chat Links Informational #128 - 2023-07-04 FEP-ae97: Client-side activity signing Implementation 4 #148 Discussions 2023-08-14 FEP-0837: Federated Marketplace Implementation 4 #147 Discussions 2023-08-17 FEP-5feb: Search indexing consent for actors Informational #154 - 2023-09-06 FEP-dc88: Formatting Mathematics Informational #161 - 2023-09-12 FEP-d8c2: OAuth 2.0 Profile for the ActivityPub API Informational #165 Discussions 2023-09-17 FEP-7628: Move actor Informational #168 Discussions 2023-09-20 FEP-37f2: a policy for calls for consensus on SWICG group decisions Informational #184 - 2023-09-28 FEP-2677: Identifying the Application Actor Informational #190 - 2023-10-14 FEP-03c1: Actors without acct-URI Informational #205 - 2023-11-10 FEP-ef61: Portable Objects Implementation 5 #209 Discussions 2023-12-06 FEP-7502: Limiting visibility to authenticated actors Informational #218 - 2023-12-24 FEP-2c59: Discovery of a Webfinger address from an ActivityPub actor Informational #226 - 2024-01-04 FEP-3264: Federated Work Coordination Implementation #255 - 2024-01-31 FEP-c5a1: To-do's Implementation #256 - 2024-01-31 FEP-61cf: The OpenWebAuth Protocol Implementation 5 #263 - 2024-02-06 FEP-73cd: Migration User Stories Informational #265 - 2024-02-07 FEP-96ff: Explicit signalling of ActivityPub Semantics Informational #272 - 2024-02-17 FEP-9fde: Mechanism for servers to expose supported operations Informational #282 - 2024-03-14 FEP-e229: Best practices for extensibility Informational #293 - 2024-04-02 FEP-3b86: Activity Intents Informational #302 - 2024-04-19 FEP-7458: Using the replies collection Informational #307 - 2024-04-26 FEP-0391: Special collection proofs Informational #310 - 2024-04-29 FEP-db0e: Authentication mechanism for non-public groups Informational #313 - 2024-05-03 FEP-cd47: Federation-friendly Addressing and Deduplication Use-Cases Informational #319 Discussions 2024-05-31 FEP-5e53: Opt-out Preference Signals Informational #327 Discussions 2024-06-09 FEP-7952: Roadmap For Actor and Object Portability Informational #435 Discussions 2024-06-20 FEP-e3e9: Actor-Relative URLs Informational #346 - 2024-06-29 FEP-d9ad: Create Conformance Tests for Fediverse Enhancement Proposals Informational #350 - 2024-07-02 FEP-e965: Move Activity for Migrations and Announce Activity for Tombstone Events Informational #352 Discussions 2024-07-05 FEP-9091: Export Actor Service Endpoint Informational #358 Discussions 2024-07-08 FEP-6fcd: Account Export Container Format Informational #357 Discussions 2024-07-11 FEP-c551: Use ECMAScript Modules to Create Conformance Tests for Fediverse Enhancement Proposals Informational #366 - 2024-07-11 FEP-a5c5: Web Syndication Methods Informational #368 Discussions 2024-07-15 FEP-c4ad: Viewership History Informational #367 Discussions 2024-07-15 FEP-c893: DOAP Informational #372 Discussions 2024-07-15 FEP-eb48: Hashtags Informational #373 Discussions 2024-07-16 FEP-eb22: Supported ActivityStreams types with NodeInfo Informational #386 Discussions 2024-07-25 FEP-c0e0: Emoji reactions Implementation 4 #384 Discussions 2024-08-08 FEP-c16b: Formatting MFM functions Informational #383 Discussions 2024-08-10 FEP-0499: Delivering to multiple inboxes with a multibox endpoint Informational #402 Discussions 2024-09-30 FEP-76ea: Conversation Threads Informational #407 Discussions 2024-10-04 FEP-1985: Signaling how an OrderedCollection is ordered Informational #417 Discussions 2024-10-10 FEP-268d: Search consent signals for objects Informational #421 Discussions 2024-10-12 FEP-b2b8: Long-form Text Informational #441 Discussions 2024-11-07 FEP-fe34: Origin-based security model Implementation #445 Discussions 2024-11-15 FEP-171b: Conversation Containers Implementation 3 #449 Discussions 2024-11-23 FEP-6606: ActivityPub client to server collections addressing conventions Informational #453 Discussions 2024-12-04 FEP-1311: Media Attachments Informational #459 Discussions 2024-12-08 FEP-7d8c: Documentation: Automation of FEP Informational #470 Discussions 2025-01-20 FEP-9967: Polls Implementation 2 #478 Discussions 2025-01-23 FEP-2277: ActivityPub core types Informational #485 Discussions 2025-01-31 FEP-a974: All Actor types should be followable Informational #487 Discussions 2025-02-05 FEP-efda: Followable objects Informational #492 Discussions 2025-02-13 FEP-f228: Backfilling conversations Implementation 13 #500 Discussions 2025-02-17 FEP-f06f: Object observers Implementation #503 Discussions 2025-02-18 FEP-dd4b: Quote Posts Informational #511 Discussions 2025-02-21 FEP-c180: Problem Details for ActivityPub Informational #531 Discussions 2025-03-11 FEP-2931: Representing context with a Collection Implementation 4 #534 Discussions 2025-03-22 FEP-5711: Inverse Properties for Collections Informational #539 Discussions 2025-03-24 FEP-044f: Consent-respecting quote posts Informational #547 Discussions 2025-04-03 FEP-1042: Peer to Peer Fediverse Identities Informational #546 Discussions 2025-04-03 FEP-4f05: Soft Deletion Informational #554 Discussions 2025-04-15 FEP-8a8e: A common approach to using the Event object type Informational #565 Discussions 2025-04-23 FEP-82f6: Actor statuses Informational #600 Discussions 2025-05-12 FEP-844e: Capability discovery Implementation 5 #624 Discussions 2025-06-14 FEP-b06c: ActivityPoll Informational #638 Discussions 2025-06-25 FEP-9098: Custom emojis Implementation #648 Discussions 2025-07-06 FEP-11dd: Context Ownership and Inheritance Informational #675 Discussions 2025-09-11 FEP-8967: Generating link previews for attached links Informational #681 Discussions 2025-09-16 FEP-1580: Move Actor Objects with a migration Collection Implementation #702 Discussions 2025-10-09 FEP-d8c8: BitTorrent Torrent Objects Informational #722 Discussions 2025-11-03 FEP-19b3: Specifying Properties of a Service Informational #719 Discussions 2025-11-04 FEP-22b6: Linking an ActivityPub Object to a HTML page and back Informational #730 Discussions 2025-11-12 FEP-f15d: Context Relocation and Removal Informational #751 Discussions 2026-01-12 FEP-ee3a: Exif metadata support Informational #752 Discussions 2026-01-13 FEP-34c1: Collection Filtering using TREE Hypermedia Vocabulary Informational #763 Discussions 2026-02-19 FEP-a427: Server Domain Migration Informational #767 Discussions 2026-02-25 FEP-fc48: Generic ActivityPub server Implementation #769 Discussions 2026-02-27 FEP-3ab2: ActivityPub Event Streaming API Informational #778 Discussions 2026-03-14 FEP-34ec: Notification Collection Endpoint Informational #782 Discussions 2026-03-15 FEP-c07e: add product type to object Implementation #786 Discussions 2026-03-15 FEP-db70: RemoveAll Collection Activity Informational #784 Discussions 2026-03-15 FEP-c195: JSONPath Filtering for ActivityPub Collection Retrieval Implementation 1 #790 Discussions 2026-03-17 FEP-f011: Full-Text Search Query Syntax for ActivityPub Implementation 1 #791 Discussions 2026-03-17 FEP-c81b: Agent Social Attribution for ActivityPub Informational #798 Discussions 2026-03-18 FEP-a1d1: ActivityPub Patch Implementation 1 #796 Discussions 2026-03-19 FEP-7b29: Federated Signup Requests Informational #822 Discussions 2026-03-26 FEP-1a11: Send Announces Containing Many Activities Informational #807 Discussions 2026-04-01 FEP-9f9f: Collections Informational #810 Discussions 2026-04-03 FEP-35b7: Fediverse Servers, Instances, and Tenants Informational #832 Discussions 2026-04-22 FEP-ae49: Semantic Routing for ActivityPub Implementation 3 #836 Discussions 2026-04-24 FEP-baf5: Administrator Collection Informational #848 Discussions 2026-05-22 FEP-bebd: Follow Invites Informational #854 Discussions 2026-06-05 FEP-5219: Groups and permissions Implementation #860 Discussions 2026-06-11 FEP-7aa9: Featuring recommendations using a dedicated collection Informational #862 Discussions 2026-06-12 FEP-8c13: Context-Authority Routing with Object Integrity Proofs for Restricted Threads Informational #870 Discussions 2026-06-27"},{"location":"final/","title":"Final","text":"Title Type Tracking issue Discussions Received Finalized FEP-a4ed: The Fediverse Enhancement Proposal Process Informational #201 - 2020-10-16 2021-01-18 FEP-8fcf: Followers collection synchronization across servers Informational #6 - 2020-10-24 2022-02-07 FEP-f1d5: NodeInfo in Fediverse Software Informational #50 - 2020-12-13 2023-06-02 FEP-400e: Publicly-appendable ActivityPub collections Informational #5 - 2021-02-16 2022-02-04 FEP-e232: Object Links Informational #14 Discussions 2022-08-01 2023-12-03 FEP-1b12: Group federation Informational #22 - 2022-11-12 2023-02-09 FEP-521a: Representing actor's public keys Implementation 6 #130 Discussions 2023-07-08 2025-06-14 FEP-67ff: FEDERATION.md Informational #157 Discussions 2023-09-05 2024-09-22 FEP-d556: Server-Level Actor Discovery Using WebFinger Implementation 7 #243 - 2024-01-20 2025-03-15 FEP-ae0c: Fediverse Relay Protocols: Mastodon and LitePub Informational #424 Discussions 2024-10-19 2025-03-14 FEP-0151: NodeInfo in Fediverse Software (2025 edition) Implementation 5 #599 Discussions 2025-05-12 2026-03-07"},{"location":"withdrawn/","title":"Withdrawn","text":"Title Type Tracking issue Discussions Received Withdrawn FEP-8c3f: Web Monetization Informational #3 - 2022-01-18 2023-10-27 FEP-2100: Unbound Group and Organization Informational #36 Discussions 2022-03-31 2025-04-06 FEP-5624: Per-object reply control policies Informational #18 Discussions 2022-08-23 2025-06-24 FEP-cb76: Content Addressed Vocabulary Informational #41 Discussions 2022-11-29 2025-04-07 FEP-2e40: The FEP Vocabulary Extension Process Informational #62 - 2023-02-13 2023-09-04 FEP-d767: Extend ActivityPub with Valueflows Informational #76 - 2023-04-02 2024-02-03 FEP-5bf0: Collection sorting and filtering Informational #80 - 2023-04-10 2025-06-14 FEP-612d: Identifying ActivityPub Objects through DNS Informational #89 - 2023-04-18 2025-10-10 FEP-a070: Ordered properties for plain JSON consumers Informational #121 - 2023-06-13 2026-04-10 FEP-07d7: A Custom URL Scheme and Web-Based Protocol Handlers for Linking to ActivityPub Resources Informational #171 - 2023-09-22 2025-04-22 FEP-6481: Specifying ActivityPub extension support with NodeInfo Informational #279 - 2024-03-12 2024-10-31 FEP-c7d3: Ownership Informational #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 Date final Tracking issue Discussions Repository silverpill @silverpill@mitra.social FINAL implementation 2025-05-12 2026-03-07 #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":"

Some softwares might wish to publish NodeInfo, but without revealing usage statistics. While the NodeInfo 2.1 schema specifies usage and usage.users as required properties, it does not require any additional properties within usage.users. Servers MUST NOT publish intentionally skewed usage statistics; instead, servers wishing to hide their usage statistics SHOULD publish an empty object:

{\n  \"usage\": {\n    \"users\": {}\n  }\n}\n
"},{"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. Attempting to use this information is very difficult to do reliably, and often hinders inteoperability. However, software version information MAY be used to block instances with known security vulnerabilities.

Instead of NodeInfo, protocol-specific capability discovery mechanisms could be used. 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:

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":""},{"location":"fep/0151/#references","title":"References","text":""},{"location":"fep/0151/#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/0391/","title":"FEP-0391: Special collection proofs","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 2024-04-29 #310 - codeberg"},{"location":"fep/0391/#summary","title":"Summary","text":"

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.

"},{"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.

"},{"location":"fep/0391/#outbox-claim","title":"Verifying presence in 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 in inbox","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.

"},{"location":"fep/0391/#followers-claim","title":"Verifying presence in 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:

"},{"location":"fep/0391/#following-claim","title":"Verifying presence in following","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:

"},{"location":"fep/0391/#liked-claim","title":"Verifying presence in liked","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.

"},{"location":"fep/0391/#likes-claim","title":"Verifying presence in 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.

"},{"location":"fep/0391/#shares-claim","title":"Verifying presence in 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.

"},{"location":"fep/0391/#replies-claim","title":"Verifying presence in 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

"},{"location":"fep/0391/#context-claim","title":"Verifying presence in 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

"},{"location":"fep/0391/#types-of-proofs","title":"Types of proofs","text":"

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.

"},{"location":"fep/0391/#replies-stamp","title":"Showing inclusion in 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:

On a Create activity where the object has inReplyTo set, the proof can be verified if all of the following are satisfied:

On 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:

Example 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:

On a Create activity where the object has context set, the proof can be verified if all of the following are satisfied:

On 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:

Example 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:

Example:

{\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:

Example:

{\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:

"},{"location":"fep/0391/#flow","title":"Obtaining and revoking stamps","text":"

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\"]

"},{"location":"fep/0391/#a-simpler-example-flow-for-issuing-and-revoking-a-like-stamp","title":"A simpler example flow for issuing and revoking a like stamp","text":"

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":""},{"location":"fep/0391/#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/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.

"},{"location":"fep/03c1/#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/044f/","title":"FEP-044f: Consent-respecting quote posts","text":"Authors Status Date received Tracking issue Discussions Repository Claire claire.fep-1d7d@sitedethib.com DRAFT 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.

"},{"location":"fep/044f/#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 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.

"},{"location":"fep/044f/#quote","title":"Representation of a quote post","text":"

A \u201cquote post\u201d is represented as an object with a quote (https://w3id.org/fep/044f#quote) attribute.

"},{"location":"fep/044f/#example","title":"Example","text":"
{\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.

"},{"location":"fep/044f/#compatibility-with-other-quote-implementations","title":"Compatibility with other quote implementations","text":"

(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.

"},{"location":"fep/044f/#example-of-quoteauthorization","title":"Example of 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

"},{"location":"fep/044f/#revoking-a-quoteauthorization","title":"Revoking a QuoteAuthorization","text":"

An approval stamp can be revoked by Deleteing the stamp.

"},{"location":"fep/044f/#QuoteRequest","title":"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.

"},{"location":"fep/044f/#example-quoterequest-activity","title":"Example 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.

"},{"location":"fep/044f/#example-accept","title":"Example 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      \"QuoteRequest\": \"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.

"},{"location":"fep/044f/#rejection","title":"Rejection","text":"

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.

"},{"location":"fep/044f/#acceptance","title":"Acceptance","text":"

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.

"},{"location":"fep/044f/#example-updated-note-object","title":"Example updated Note object","text":"
{\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      \"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 object for this object

"},{"location":"fep/044f/#revocation-of-a-quote-post","title":"Revocation of a quote post","text":""},{"location":"fep/044f/#revoking-a-previously-accepted-quote-post","title":"Revoking a previously-accepted quote post","text":"

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.

"},{"location":"fep/044f/#handling-a-revocation","title":"Handling a revocation","text":"

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.

"},{"location":"fep/044f/#opportunistic-re-verification-of-quote-approvals","title":"Opportunistic re-verification of quote approvals","text":"

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.

"},{"location":"fep/044f/#server-behavior-considerations","title":"Server behavior considerations","text":"

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.

"},{"location":"fep/044f/#security-considerations","title":"Security considerations","text":"

(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

"},{"location":"fep/044f/#implementations","title":"Implementations","text":""},{"location":"fep/044f/#references","title":"References","text":""},{"location":"fep/044f/#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/0499/","title":"FEP-0499: Delivering to multiple inboxes with a multibox endpoint","text":"Authors Status Date received Tracking issue Discussions Repository a DRAFT 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.

"},{"location":"fep/0499/#prior-art","title":"Prior art","text":"

(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:

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).

"},{"location":"fep/0499/#examples","title":"Examples","text":"

(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":""},{"location":"fep/0499/#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/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 "},{"location":"fep/07d7/#summary","title":"Summary","text":"

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

"},{"location":"fep/07d7/#21-intents","title":"2.1 Intents","text":"

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.

"},{"location":"fep/07d7/#22-example-links-using-the-scheme","title":"2.2 Example links using the scheme","text":"

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>

"},{"location":"fep/07d7/#3-protocol-handlers","title":"3. Protocol Handlers","text":""},{"location":"fep/07d7/#31-retrieving-hyperlinked-objects","title":"3.1 Retrieving hyperlinked objects","text":"

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:

"},{"location":"fep/07d7/#33-servers","title":"3.3 Servers","text":"

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.

"},{"location":"fep/07d7/#34-clients","title":"3.4 Clients","text":"

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.

"},{"location":"fep/07d7/#references","title":"References","text":""},{"location":"fep/07d7/#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/0837/","title":"FEP-0837: Federated Marketplace","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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 `OfferAgreement` activity\n  Alice ->> Bob: Alice sends `AcceptAgreement` 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:

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:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/0837\"\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    \"resourceConformsTo\": \"https://www.wikidata.org/wiki/Q11442\",\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    \"https://w3id.org/fep/0837\"\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\": \"Local organic carrots\",\n  \"content\": \"Great quality with all the rain, harvested yesterday.\",\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/Q81\",\n    \"resourceQuantity\": {\n      \"hasUnit\": \"pound\",\n      \"hasNumericalValue\": \"1\"\n    },\n    \"availableQuantity\": {\n      \"hasUnit\": \"pound\",\n      \"hasNumericalValue\": \"150\"\n    },\n    \"minimumQuantity\": {\n      \"hasUnit\": \"pound\",\n      \"hasNumericalValue\": \"5\"\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\": \"currencyAmount\",\n      \"hasNumericalValue\": \"0.89\"\n    }\n  },\n  \"unitBased\": \"true\",\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\": [\n    \"https://www.w3.org/ns/activitystreams\"\n  ],\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.

"},{"location":"fep/0837/#responding-to-a-proposal","title":"Responding to a proposal","text":""},{"location":"fep/0837/#agreements","title":"Agreements","text":"

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 AcceptAgreement activity back to the interested party.

In the second case, the proposer sends RejectAgreement activity. The interested party MAY send OfferAgreement activities many times until agreement is reached.

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 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 OfferAgreement activity:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/0837\"\n  ],\n  \"type\": \"OfferAgreement\",\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\": \"currencyAmount\",\n        \"hasNumericalValue\": \"30.00\"\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 AcceptAgreement activity MUST be the id of the OfferAgreement activity previously sent to the actor.

The AcceptAgreement activity MUST have a 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 AcceptAgreement activity.

The quantities specified in the finalized agreement MUST match the quantities specified in the Agreement object from the OfferAgreement 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    \"https://w3id.org/fep/0837\"\n  ],\n  \"type\": \"AcceptAgreement\",\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\": \"currencyAmount\",\n        \"hasNumericalValue\": \"30.00\"\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 RejectAgreement activity MUST be the id of the OfferAgreement activity previously sent to the actor.

Activity MAY contain content property indicating the reason for rejection.

Example:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/0837\"\n  ],\n  \"type\": \"RejectAgreement\",\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":""},{"location":"fep/0837/#references","title":"References","text":""},{"location":"fep/0837/#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/0ea0/","title":"FEP-0ea0: Payment Links","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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:

Payment links MUST be added to attachment array of an actor or an object.

"},{"location":"fep/0ea0/#examples","title":"Examples","text":"

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.

"},{"location":"fep/0ea0/#references","title":"References","text":""},{"location":"fep/0ea0/#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/1042/","title":"FEP-1042: Peer to Peer Fediverse Identities","text":"Authors Status Date received Tracking issue Discussions Repository Mauve Signweaver contact@mauve.moe DRAFT 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.

"},{"location":"fep/1042/#webfinger","title":"Webfinger","text":"

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.

"},{"location":"fep/1042/#url-links-in-ap-objects","title":"URL links in AP Objects","text":"

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.

"},{"location":"fep/1042/#gateways","title":"Gateways","text":"

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.

"},{"location":"fep/1042/#examples","title":"Examples","text":"

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":""},{"location":"fep/1042/#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/11dd/","title":"FEP-11dd: Context Ownership and Inheritance","text":"Authors Status Date received Tracking issue Discussions Repository Julian Lam julian@nodebb.org 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":"

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).

"},{"location":"fep/11dd/#what-is-this-for","title":"What is this for?","text":"

This proposal aims to extend these guidelines further by codifying:

  1. That a context must declare an owner via context.attributedTo.
  2. Where and when a context may be inherited by new objects.
"},{"location":"fep/11dd/#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/11dd/#context-ownership","title":"Context Ownership","text":"

A context MUST have an owner. The following are upgrades to FEP 7888 that pertain to ownership:

  1. A context MUST be resolvable.
  2. When resolved the context MUST contain:
  3. context.attributedTo MUST resolve to an actor (ActivityPub \u00a74.1 Actor Objects).
"},{"location":"fep/11dd/#example","title":"Example","text":"

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.)

"},{"location":"fep/11dd/#references","title":"References","text":""},{"location":"fep/11dd/#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/1311/","title":"FEP-1311: Media Attachments","text":"Authors Status Date received Tracking issue Discussions Repository Helge @helge@mymath.rocks DRAFT 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.

"},{"location":"fep/1311/#specifying-basic-properties","title":"Specifying basic properties","text":"

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.

"},{"location":"fep/1311/#properties-of-the-linked-file","title":"Properties of the linked file","text":"

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.

"},{"location":"fep/1311/#properties-of-an-image","title":"Properties of an image","text":"

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

where at least focalPoint is user defined. There are a lot of other properties one can consider for media, e.g.

In 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.

"},{"location":"fep/1311/#mixed-media-content","title":"Mixed media content","text":"

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":""},{"location":"fep/1311/#posts","title":"Posts","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":"FEP-1580: Move Actor Objects with a 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":"

(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:

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.

"},{"location":"fep/1580/#specification","title":"Specification","text":"

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.

"},{"location":"fep/1580/#terminology","title":"Terminology","text":"

As used in this document:

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.

"},{"location":"fep/1580/#preconditions","title":"Preconditions","text":"

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.

"},{"location":"fep/1580/#target-instance","title":"Target Instance","text":""},{"location":"fep/1580/#ingest","title":"Ingest","text":"

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.

"},{"location":"fep/1580/#examples_1","title":"Examples","text":"

(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.

"},{"location":"fep/1580/#3rd-party-instances","title":"3rd-Party Instances","text":"

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.

"},{"location":"fep/1580/#migration-routine","title":"Migration Routine","text":"

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.

"},{"location":"fep/1580/#passive-updates","title":"Passive Updates","text":"

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.

"},{"location":"fep/1580/#retroactive-migration","title":"Retroactive Migration","text":"

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:

"},{"location":"fep/1580/#pre-move-moderation","title":"Pre-Move Moderation","text":"

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:

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.

"},{"location":"fep/1580/#examples_2","title":"Examples","text":"

(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

"},{"location":"fep/1580/#moderation","title":"Moderation","text":"

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:

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.

"},{"location":"fep/1580/#performance","title":"Performance","text":"

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).

"},{"location":"fep/1580/#compatibility","title":"Compatibility","text":"

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.

"},{"location":"fep/1580/#supplementary-information","title":"Supplementary Information","text":""},{"location":"fep/1580/#acknowledgements","title":"Acknowledgements","text":"

Thank 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.

"},{"location":"fep/1580/#references","title":"References","text":""},{"location":"fep/1580/#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/#footnotes","title":"Footnotes","text":"
  1. 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

  2. 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

  3. Members of as:audience, as:to, as:bto, as:cc, as:bcc, and any other properties used for scoping visibility or delivery.\u00a0\u21a9

  4. \"Ownership\" in this document is defined according to FEP-fe34: Ownership \u21a9

  5. the CURIE for the collection is, maybe unhelpfully, migration:migration to refer to https://w3id.org/fep/1580/migration \u21a9

  6. \"Push\" mode migration, as defined in FEP-7628 \u21a9

  7. 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

  8. Until the next heading.\u00a0\u21a9

  9. 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

"},{"location":"fep/171b/","title":"FEP-171b: Conversation Containers","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social 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":"

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:

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.

"},{"location":"fep/171b/#top-level-post","title":"Top-level post","text":"

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.

"},{"location":"fep/171b/#interactions","title":"Interactions","text":"

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.

"},{"location":"fep/171b/#moderation","title":"Moderation","text":"

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).

"},{"location":"fep/171b/#backfilling","title":"Backfilling","text":"

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:

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":""},{"location":"fep/171b/#implementations","title":"Implementations","text":""},{"location":"fep/171b/#references","title":"References","text":""},{"location":"fep/171b/#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/1970/","title":"FEP-1970: Chat Links","text":"Authors Status Date received Tracking issue Discussions Repository John Livingston git@john-livingston.fr DRAFT 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:

Chat links MUST be added to the attachment array of an actor or an object.

"},{"location":"fep/1970/#examples","title":"Examples","text":""},{"location":"fep/1970/#basic-object-attachment","title":"Basic object attachment","text":"
{\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":""},{"location":"fep/1970/#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/1985/","title":"FEP-1985: Signaling how an OrderedCollection is ordered","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 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.

"},{"location":"fep/1985/#terms-defined","title":"Terms defined","text":"orderType 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
\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":""},{"location":"fep/1985/#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/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.

"},{"location":"fep/19b3/#property-values","title":"Property Values","text":"

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.

"},{"location":"fep/19b3/#suggested-property-values","title":"Suggested Property Values","text":"

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

"},{"location":"fep/19b3/#support","title":"Support","text":"

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.

"},{"location":"fep/19b3/#appendix-the-json-ld-problem","title":"Appendix: The JSON-LD problem","text":"

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.

"},{"location":"fep/19b3/#references","title":"References","text":""},{"location":"fep/19b3/#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/1a11/","title":"FEP-1a11: Send Announces Containing Many Activities","text":"Authors Status Date received Tracking issue Discussions Repository Rimu Atkinson contact@piefed.social DRAFT 2026-04-01 #807 Discussions codeberg"},{"location":"fep/1a11/#summary","title":"Summary","text":"

This proposal describes a mechanism for sending multiple activities in a single POST. The core idea is to extend FEP 1b12 slightly by changing the object property on Announce activities from an object to a list/array of objects.

"},{"location":"fep/1a11/#motivation","title":"Motivation","text":"

Currently, ActivityPub involves sending a HTTP POST request to followers whenever something notable happens. As a user can do a great number of notable things (posting content, liking content, following others) each minute and there can be thousands of instances to send to, a great many POST requests can be sent in a short amount of time.

For example if 5 people cast 20 votes and there are 500 instances, the instance hosting the community containing the posts being voted on must send 5 * 20 * 500 = 50,000 HTTP POSTs.

Each POST involves overhead and uses resources at both ends while it occurs. To reduce this overhead and resource use, this proposal describes a mechanism for sending multiple activities in a single POST.

Benefits include: decreased network traffic, higher efficiency, greater scalability, lowered carbon emissions and lower server costs.

"},{"location":"fep/1a11/#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.

For the purposes of this document, a \"Origin Server\" is the location where a user is logged in, and that sends the Actor's Activity. A \"Remote Server\" is another location on the Internet that will receive those Activities.

"},{"location":"fep/1a11/#proposal","title":"Proposal","text":"

The majority of FEP 1b12-based ActivityPub traffic involves the transfer of an Announce with a up/down vote in it. This FEP only addresses those activities - all other traffic is processed in the usual way.

Origin Servers SHOULD continue to send individual POSTs with a single Activity in them, just as they do now. For votes, which are not especially time-sensitive, they MAY instead keep the Activity unsent in a queue.

Origin Servers SHOULD include metadata with each queued Activity recording the community it is being sent from and the instance it is destined for.

Once an implementation-specific threshold is reached (e.g. a certain amount of time has passed or a queue has grown to a certain size) the Activities from each community will be batched together under one Announce (per community) and that batch federated to each destination instance.

A batch of Activities MUST be comprised of an Announce that has an object attribute that is an OrderedCollection of Like, Dislike, Undo(Like) or Undo(Dislike) activities.

Remote Servers SHOULD process the Activities' object property serially in the order provided.

The batch MUST be HTTP Signed by a Group actor, the community that generated the batch, which SHOULD be find-able using WebFinger. The signature SHOULD include the date the signing took place, to make replay attacks less likely. This method of signing is widely used on the Fediverse already.

Remote Servers MAY use HTTP Signature verification to ensure the integrity of the received Activity.

"},{"location":"fep/1a11/#post-example","title":"POST Example","text":""},{"location":"fep/1a11/#existing-old-style-posts","title":"Existing old-style POSTs","text":"
POST server.tld/inbox\nSignature: a69F3mfddd99dGjmvjdjjs81e12jn121Gkm1\nDigest: asdfasdfasdf\n{\n  \"actor\": \"https://lemmy.world/c/nostupidquestions\",\n  \"to\": [\n    \"https://www.w3.org/ns/activitystreams#Public\"\n  ],\n  \"object\": {\n    \"id\": \"https://lemmy.world/activities/like/aa6d9456-0960-4b9d15-561d1cef2991\",\n    \"actor\": \"https://lemmy.world/u/SpaceNo\",\n    \"object\": \"https://kbin.social/m/nostupidquestions@lemmy.world/t/433370/-/comment/2965\",\n    \"type\": \"Like\",\n    \"audience\": \"https://lemmy.world/c/nostupidquestions\"\n  },\n  \"cc\": [\n    \"https://lemmy.world/c/nostupidquestions/followers\"\n  ],\n  \"type\": \"Announce\",\n  \"id\": \"https://lemmy.world/activities/announce/e49d182c-db32-4a10-bb-c57de890cd9e\",\n  \"@context\": [\n     ...\n  ],\n}\n\n\nPOST server.tld/inbox\nSignature: b69F3mfddd99dGjmvjdjjs81e12jn121Gkm1\nDigest: asdfasdfasdf\n{\n  \"actor\": \"https://lemmy.world/c/nostupidquestions\",\n  \"to\": [\n    \"https://www.w3.org/ns/activitystreams#Public\"\n  ],\n  \"object\": {\n    \"id\": \"https://lemmy.world/activities/like/aa6d9456-094bf0-9d15-561d1cef299e\",\n    \"actor\": \"https://lemmy.world/u/Peter\",\n    \"object\": \"https://kbin.social/m/nostupidquestions@lemmy.world/t/433370/-/comment/2965\",\n    \"type\": \"Like\",\n    \"audience\": \"https://lemmy.world/c/nostupidquestions\"\n  },\n  \"cc\": [\n    \"https://lemmy.world/c/nostupidquestions/followers\"\n  ],\n  \"type\": \"Announce\",\n  \"id\": \"https://lemmy.world/activities/announce/e49d182c-db32-4a10-bb-c57de890cd9e\",\n  \"@context\": [\n     ...\n  ],\n}\n\n\nPOST server.tld/inbox\nSignature: c69F3mfddd99dGjmvjdjjs81e12jn121Gkm1\nDigest: asdfasdfasdf\n{\n  \"actor\": \"https://lemmy.world/c/nostupidquestions\",\n  \"to\": [\n    \"https://www.w3.org/ns/activitystreams#Public\"\n  ],\n  \"object\": {\n    \"id\": \"https://lemmy.world/activities/like/aa6d9456-0960-4bf0-9d15-56cef2994\",\n    \"actor\": \"https://lemmy.world/u/Rimu\",\n    \"object\": \"https://kbin.social/m/nostupidquestions@lemmy.world/t/433370/-/comment/2965\",\n    \"type\": \"Like\",\n    \"audience\": \"https://lemmy.world/c/nostupidquestions\"\n  },\n  \"cc\": [\n    \"https://lemmy.world/c/nostupidquestions/followers\"\n  ],\n  \"type\": \"Announce\",\n  \"id\": \"https://lemmy.world/activities/announce/e49d182c-db32-4a10-bb-c57de890cd9e\",\n  \"@context\": [\n     ...\n  ],\n}\n
"},{"location":"fep/1a11/#new-and-improved-fep-1a11-style-post","title":"New and improved fep-1a11-style POST","text":"
POST server.tld/inbox\nSignature: asdfasdfasdf\nDigest: asdfasdfasdf\n{\n  \"actor\": \"https://lemmy.world/c/nostupidquestions\",\n  \"to\": [\n    \"https://www.w3.org/ns/activitystreams#Public\"\n  ],\n  \"object\": {\n    \"type\": \"OrderedCollection\",\n    \"id\": \"https://lemmy.world/activities/abcd9456-0960-4b9d15-561d1cef2991\",\n    \"totalItems\": 3,\n    \"orderedItems\": [\n        {\n          \"id\": \"https://lemmy.world/activities/like/aa6d9456-0960-4b9d15-561d1cef2991\",\n          \"actor\": \"https://lemmy.world/u/SpaceNo\",\n          \"object\": \"https://kbin.social/m/nostupidquestions@lemmy.world/t/433370/-/comment/2965\",\n          \"type\": \"Like\",\n          \"audience\": \"https://lemmy.world/c/nostupidquestions\"\n        },\n        {\n          \"id\": \"https://lemmy.world/activities/like/aa6d9456-094bf0-9d15-561d1cef299e\",\n          \"actor\": \"https://lemmy.world/u/Peter\",\n          \"object\": \"https://kbin.social/m/nostupidquestions@lemmy.world/t/433370/-/comment/2965\",\n          \"type\": \"Like\",\n          \"audience\": \"https://lemmy.world/c/nostupidquestions\"\n        },\n        {\n          \"id\": \"https://lemmy.world/activities/like/aa6d9456-0960-4bf0-9d15-56cef2994\",\n          \"actor\": \"https://lemmy.world/u/Rimu\",\n          \"object\": \"https://kbin.social/m/nostupidquestions@lemmy.world/t/433370/-/comment/2965\",\n          \"type\": \"Like\",\n          \"audience\": \"https://lemmy.world/c/nostupidquestions\"\n        }\n    ],\n  }\n  \"cc\": [\n    \"https://lemmy.world/c/nostupidquestions/followers\"\n  ],\n  \"type\": \"Announce\",\n  \"id\": \"https://lemmy.world/activities/announce/e49d182c-db32-4a10-bb-c57de890cd9e\",\n  \"@context\": [\n    ...\n  ],\n}\n
"},{"location":"fep/1a11/#references","title":"References","text":""},{"location":"fep/1a11/#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/1b12/","title":"FEP-1b12: Group federation","text":"Authors Status Date received Date final Tracking issue Discussions Repository Felix Ableitner me@nutomic.com FINAL 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.

"},{"location":"fep/1b12/#history","title":"History","text":"

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.

"},{"location":"fep/1b12/#following-a-group","title":"Following a Group","text":"

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.

"},{"location":"fep/1b12/#audience-property","title":"Audience property","text":"

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.

"},{"location":"fep/1b12/#threads-and-comments","title":"Threads and comments","text":"

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.

"},{"location":"fep/1b12/#the-announce-activity","title":"The Announce activity","text":"

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.

"},{"location":"fep/1b12/#group-moderation","title":"Group moderation","text":"

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.

"},{"location":"fep/1b12/#implementations","title":"Implementations","text":"

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.

"},{"location":"fep/1b12/#references","title":"References","text":""},{"location":"fep/1b12/#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/2100/","title":"FEP-2100: Unbound Group and Organization","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Diogo Peralta Cordeiro mail@diogo.site WITHDRAWN 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.

"},{"location":"fep/2100/#notation-and-definitions","title":"Notation and Definitions","text":"

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.

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 has gs:unbound = false","text":""},{"location":"fep/2100/#2-group-a-follows-group-b-which-has-gsunbound-true","title":"2. Group A follows Group B which has gs:unbound = true","text":""},{"location":"fep/2100/#3-group-a-follows-group-b-which-has-no-gsunbound-attribute","title":"3. Group A follows Group B which has no gs:unbound attribute","text":""},{"location":"fep/2100/#4-forwarding-from-inbox","title":"4. Forwarding from Inbox","text":""},{"location":"fep/2100/#references","title":"References","text":""},{"location":"fep/2100/#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/2277/","title":"FEP-2277: ActivityPub core types","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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:

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.

"},{"location":"fep/2277/#core-object-types","title":"Core object types","text":"

Activity Streams 2.0 standard defines 8 core object types:

Unfortunately, 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:

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:

  1. If object has inbox and outbox properties, return Actor.
  2. If object has publicKeyMultibase property, return VerificationMethod.
  3. If object has publicKeyPem property, return PublicKey.
  4. If object has href property, return Link.
  5. If object has actor property, return Activity.
  6. If object has items, orderedItems, totalItems, partOf, first, last, next, prev or current property, return Collection.
  7. Otherwise, return 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\".

"},{"location":"fep/2277/#json-ld","title":"JSON-LD","text":"

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.

"},{"location":"fep/2277/#references","title":"References","text":""},{"location":"fep/2277/#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/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.rocks DRAFT 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":""},{"location":"fep/22b6/#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/2677/","title":"FEP-2677: Identifying the Application Actor","text":"Authors Status Date received Tracking issue Discussions Repository Helge helge.krueger@gmail.com DRAFT 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.

"},{"location":"fep/2677/#requirements","title":"Requirements","text":""},{"location":"fep/2677/#application-actor","title":"Application Actor","text":"

We will first define, what we mean by an application actor. We will impose two requirements on them

  1. It's an [ActivityPub] Actor of type Application.
  2. It can be retrieved without authentication, e.g. with an unsigned HTTP request.

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:

  1. A fixed path for the application actor
  2. A fixed path for application information instead of aiming to attach it to the application actor.

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:

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.

"},{"location":"fep/2677/#currently-implemented-application-actors","title":"Currently implemented application actors","text":"Software Application Actor URI Bovine 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":""},{"location":"fep/2677/#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/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.com DRAFT 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.

"},{"location":"fep/268d/#background","title":"Background","text":"

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.

"},{"location":"fep/268d/#requirements","title":"Requirements","text":"

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 URI http://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":"FEP-2931: Representing context with a Collection","text":"Authors Status Type Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 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.

"},{"location":"fep/2931/#the-concept-of-a-canonical-context-collection","title":"The concept of a canonical context collection","text":"

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:

"},{"location":"fep/2931/#example-representation-of-a-context-collection","title":"Example representation of a context Collection","text":"

(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.

"},{"location":"fep/2931/#producing-and-maintaining-a-context-collection","title":"Producing and maintaining a context Collection","text":"

(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:

There 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\".

"},{"location":"fep/2931/#semantic-confusion-between-objects-and-collections-of-objects","title":"Semantic confusion between objects and collections of objects","text":"

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":""},{"location":"fep/2931/#references","title":"References","text":""},{"location":"fep/2931/#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/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.name DRAFT 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:

[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":""},{"location":"fep/2c59/#context","title":"Context","text":"

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.

"},{"location":"fep/2c59/#references","title":"References","text":""},{"location":"fep/2c59/#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/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.com WITHDRAWN 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.

"},{"location":"fep/2e40/#process-to-add-a-new-term-to-the-fep-vocabulary","title":"Process to add a new Term to the FEP Vocabulary","text":"

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].

  1. The $TERM MUST be indicative of its function. Avoid generic terms such as \"myProperty\" or \"foo\".
  2. If there are copyright, trademark, or any intellectual property rights concerns, the addition and use MUST be authorized in writing by the intellectual property rights holder under a F/RAND license. Examples include terms that use trademarked brand names, property names that utilize the titles of copyrighted works, and patented technology that would cause the use of the extension to require licensing a patent.
  3. Any addition MUST NOT create unreasonable legal, security, moral, or privacy issues that will result in direct harm to others. Examples of unacceptable additions include any containing racist language, technologies used to persecute minority populations, and unconsented pervasive tracking.

Furthermore, the author MAY perform the following action

  1. Add a new term of the form fep-xxxx-$TERM to /feps/assets/fep-2e40/namespace.json.
  2. Add a description for this term to /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.

"},{"location":"fep/2e40/#example-create-fep-term-eventsource","title":"Example \"Create FEP term: eventSource\"","text":"

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.

"},{"location":"fep/2e40/#acknowledgements","title":"Acknowledgements","text":"

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":""},{"location":"fep/2e40/#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/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":""},{"location":"fep/3264/","title":"FEP-3264: Federated Work Coordination","text":"Authors Status Type Date received Tracking issue Discussions Repository Lynn Foster lynn@mikorizal.org DRAFT implementation 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, with optional connected agreements, that constitute a body of scheduled work with defined deliverable(s).

The representation of a plan is a JSON document with the following properties:

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:

A Commitment is defined as A planned economic flow that has been scheduled or promised by an agent to another agent.

Example:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    {\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://project.example/resourcespecs/apple-picking\",\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://project.example/resourcespecs/apple-picking\",\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://project.example/resourcespecs/apple-picking\",\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://project.example/resourcespecs/apples\",\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://project.example/resourcespecs/apples\",\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://project.example/resourcespecs/cider-press\",\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://project.example/resourcespecs/manual-work\",\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://project.example/resourcespecs/manual-work\",\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://project.example/resourcespecs/apple-cider\",\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://project.example/resourcespecs/food-scraps\",\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:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    {\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\": \"vf:hasUnit\",\n      \"hasNumericalValue\": \"vf: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://project.example/resourcespecs/manual-work\",\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, by the backend software.

The representation of an economic event is a JSON document with the following properties:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    {\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\": \"vf:hasUnit\",\n      \"hasNumericalValue\": \"vf: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://project.example/resourcespecs/manual-work\",\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":""},{"location":"fep/3264/#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/34c1/","title":"FEP-34c1: Collection Filtering using TREE Hypermedia Vocabulary","text":"Authors Status Date received Tracking issue Discussions Repository Fred Hauschel @naturzukunft2026@mastodon.social DRAFT 2026-02-19 #763 Discussions codeberg"},{"location":"fep/34c1/#summary","title":"Summary","text":"

This FEP proposes using the TREE Hypermedia Vocabulary for client-initiated filter requests on ActivityPub Collections (especially Inbox). Clients can send a filter as a JSON-LD object via HTTP POST to retrieve a filtered subset of the collection.

This enables use cases such as: - Home Timeline: Content lifecycle activities (Create, Update, Delete, Announce) from followed actors, visible to public or followers - Mentions: Activities addressed to the actor via as:to or as:cc - Private Messages: Activities not addressed to as:Public (DMs, followers-only, group messages) - Media Filter: Only activities with images or videos

"},{"location":"fep/34c1/#context","title":"Context","text":"

The context document for this ActivityPub extension is at https://w3id.org/fep/34c1/context. Its contents are as follows:

{\n  \"@context\": {\n    \"fep34c1\": \"https://w3id.org/fep/34c1#\",\n    \"FilterRequest\": \"fep34c1:FilterRequest\",\n    \"pageSize\": {\n      \"@id\": \"fep34c1:pageSize\",\n      \"@type\": \"http://www.w3.org/2001/XMLSchema#nonNegativeInteger\"\n    },\n    \"myFollowees\": { \"@id\": \"fep34c1:myFollowees\", \"@type\": \"@id\" },\n    \"myAudience\": { \"@id\": \"fep34c1:myAudience\", \"@type\": \"@id\" }\n  }\n}\n
"},{"location":"fep/34c1/#defined-terms","title":"Defined Terms","text":""},{"location":"fep/34c1/#fep34c1filterrequest","title":"fep34c1:FilterRequest","text":"URI https://w3id.org/fep/34c1#FilterRequest Notes A container object for a filter query. It holds one or more tree:relation entries that define the filter criteria, and an optional fep34c1:pageSize to control pagination. Domain (none) Range (none)"},{"location":"fep/34c1/#fep34c1pagesize","title":"fep34c1:pageSize","text":"URI https://w3id.org/fep/34c1#pageSize Notes The maximum number of items per page in the filtered response. Domain fep34c1:FilterRequest Range xsd:nonNegativeInteger Functional Yes"},{"location":"fep/34c1/#fep34c1myfollowees","title":"fep34c1:myFollowees","text":"URI https://w3id.org/fep/34c1#myFollowees Notes Server-resolved placeholder. When used as tree:value, the server MUST replace it with the set of actor IRIs from the following collection of the authenticated (requesting) actor."},{"location":"fep/34c1/#fep34c1myaudience","title":"fep34c1:myAudience","text":"URI https://w3id.org/fep/34c1#myAudience Notes Server-resolved placeholder. When used as tree:value, the server MUST resolve it to all collection IRIs in which the authenticated actor is known to be a member. This includes at minimum the followers collections of followed actors, and MAY include group memberships, lists, or other collections as the server discovers them."},{"location":"fep/34c1/#motivation","title":"Motivation","text":""},{"location":"fep/34c1/#problem","title":"Problem","text":"

ActivityPub defines Collections (Inbox, Outbox, Followers, etc.) but no mechanism for clients to request filtered views. Clients must:

  1. Fetch all items with pagination
  2. Filter and sort locally
  3. Transfer unnecessarily large amounts of data

For a \"Home Timeline\" (activities from followees), this is particularly inefficient since the inbox also contains follow requests, likes, and other irrelevant activities.

"},{"location":"fep/34c1/#existing-approaches","title":"Existing Approaches","text":"Approach Problem FEP-5bf0 WITHDRAWN (June 2025); focused on server-side views, not client queries Hydra GET-oriented (URL templates), not for POST queries SPARQL Too complex for simple filters, security risks Proprietary Mastodon's /api/v1/timelines/home is not standardized"},{"location":"fep/34c1/#why-tree","title":"Why TREE?","text":"

The TREE Hypermedia Vocabulary offers:

"},{"location":"fep/34c1/#specification","title":"Specification","text":""},{"location":"fep/34c1/#1-filter-endpoint","title":"1. Filter Endpoint","text":"

Servers MAY provide a filter endpoint for collections:

POST /ap/actors/{username}/inbox/filter\nContent-Type: application/ld+json\n

The endpoint MUST be announced in the Collection object via the tree:search property:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/tree\"\n  ],\n  \"type\": \"OrderedCollection\",\n  \"id\": \"https://example.com/ap/actors/alice/inbox\",\n  \"tree:search\": {\n    \"@type\": \"fep34c1:FilterEndpoint\",\n    \"tree:template\": \"https://example.com/ap/actors/alice/inbox/filter\"\n  }\n}\n
"},{"location":"fep/34c1/#2-filter-request-format","title":"2. Filter Request Format","text":"

A filter request is a JSON-LD object of type fep34c1:FilterRequest:

{\n  \"@context\": {\n    \"tree\": \"https://w3id.org/tree#\",\n    \"fep34c1\": \"https://w3id.org/fep/34c1#\",\n    \"as\": \"https://www.w3.org/ns/activitystreams#\",\n    \"rdf\": \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n  },\n  \"@type\": \"fep34c1:FilterRequest\",\n  \"tree:relation\": [\n    {\n      \"@type\": \"tree:EqualToRelation\",\n      \"tree:path\": { \"@id\": \"rdf:type\" },\n      \"tree:value\": [\n        { \"@id\": \"as:Create\" },\n        { \"@id\": \"as:Update\" },\n        { \"@id\": \"as:Delete\" },\n        { \"@id\": \"as:Announce\" }\n      ]\n    },\n    {\n      \"@type\": \"tree:EqualToRelation\",\n      \"tree:path\": { \"@id\": \"as:actor\" },\n      \"tree:value\": { \"@id\": \"fep34c1:myFollowees\" }\n    },\n    {\n      \"@type\": \"tree:EqualToRelation\",\n      \"tree:path\": [{ \"@id\": \"as:to\" }, { \"@id\": \"as:cc\" }],\n      \"tree:value\": [\n        { \"@id\": \"https://www.w3.org/ns/activitystreams#Public\" },\n        { \"@id\": \"fep34c1:myAudience\" }\n      ]\n    }\n  ],\n  \"fep34c1:pageSize\": 20\n}\n
"},{"location":"fep/34c1/#3-relation-types","title":"3. Relation Types","text":""},{"location":"fep/34c1/#31-must-be-supported-core","title":"3.1 MUST be Supported (Core)","text":"Relation Semantics SPARQL Equivalent tree:EqualToRelation Value is equal to ?x = ?value tree:NotEqualToRelation Value is not equal to ?x != ?value tree:GreaterThanRelation Value is greater than ?x > ?value tree:LessThanRelation Value is less than ?x < ?value tree:GreaterThanOrEqualToRelation Value is greater than or equal to ?x >= ?value tree:LessThanOrEqualToRelation Value is less than or equal to ?x <= ?value"},{"location":"fep/34c1/#32-should-be-supported-recommended","title":"3.2 SHOULD be Supported (Recommended)","text":"Relation Semantics SPARQL Equivalent tree:PrefixRelation Value starts with STRSTARTS(?x, ?value) tree:SubstringRelation Value contains CONTAINS(?x, ?value) tree:SuffixRelation Value ends with STRENDS(?x, ?value)"},{"location":"fep/34c1/#4-special-values","title":"4. Special Values","text":""},{"location":"fep/34c1/#41-fep34c1myfollowees-dynamic-followee-list","title":"4.1 fep34c1:myFollowees - Dynamic Followee List","text":"

The value fep34c1:myFollowees is a server-resolved placeholder. When used as tree:value, the server MUST replace it with the set of actor IRIs from the following collection of the authenticated (requesting) actor.

This means a relation like:

{\n  \"@type\": \"tree:EqualToRelation\",\n  \"tree:path\": { \"@id\": \"as:actor\" },\n  \"tree:value\": { \"@id\": \"fep34c1:myFollowees\" }\n}\n

is equivalent to: \"Return only activities where the as:actor property matches one of the actor IRIs in the requesting actor's following collection.\" In other words, only activities authored by actors that the requesting user follows will be included in the result.

"},{"location":"fep/34c1/#42-fep34c1myaudience-dynamic-audience-collections","title":"4.2 fep34c1:myAudience - Dynamic Audience Collections","text":"

The value fep34c1:myAudience is a server-resolved placeholder. When used as tree:value, the server MUST resolve it to all collection IRIs in which the authenticated actor is known to be a member. This includes at minimum the followers collections of followed actors, and MAY include group memberships, lists, or other collections.

This is useful for audience filtering: Fediverse servers typically address followers-only posts with the actor's followers collection IRI in as:to or as:cc.

Note: These are explicit FEP-34c1 placeholder properties rather than reused ActivityStreams terms. An earlier version of this spec used as:following and as:followers as placeholders, but this was semantically incorrect \u2014 as:followers denotes the collection itself, not its items (see discussion). Using dedicated FEP-34c1 properties makes the server-resolved semantics explicit and avoids overloading AS2 vocabulary. An alternative approach using SHACL Property Paths was considered but rejected due to the complexity of expressing pagination traversal in SHACL path expressions.

"},{"location":"fep/34c1/#43-multiple-values-or-conjunction","title":"4.3 Multiple Values (OR Conjunction)","text":"

If tree:value is an array, the values are combined with OR:

{\n  \"tree:value\": [\n    { \"@id\": \"as:Create\" },\n    { \"@id\": \"as:Update\" },\n    { \"@id\": \"as:Delete\" },\n    { \"@id\": \"as:Announce\" }\n  ]\n}\n

Semantics: rdf:type = as:Create OR rdf:type = as:Update OR rdf:type = as:Delete OR rdf:type = as:Announce

"},{"location":"fep/34c1/#5-multiple-paths-union","title":"5. Multiple Paths (UNION)","text":"

If tree:path is an array, the filter matches if any of the specified paths contains a matching value. This is equivalent to a UNION in SPARQL.

{\n  \"@type\": \"tree:EqualToRelation\",\n  \"tree:path\": [{ \"@id\": \"as:to\" }, { \"@id\": \"as:cc\" }],\n  \"tree:value\": { \"@id\": \"https://example.com/ap/actors/alice\" }\n}\n

Semantics: \"Match if the value appears in as:to OR in as:cc.\"

This is particularly useful for audience filtering, since ActivityPub activities use both as:to and as:cc for addressing.

"},{"location":"fep/34c1/#6-combination-of-relations-and-conjunction","title":"6. Combination of Relations (AND Conjunction)","text":"

Multiple tree:relation entries are combined with AND:

{\n  \"tree:relation\": [\n    { \"@type\": \"tree:EqualToRelation\", \"tree:path\": {\"@id\": \"rdf:type\"}, \"tree:value\": {\"@id\": \"as:Create\"} },\n    { \"@type\": \"tree:EqualToRelation\", \"tree:path\": {\"@id\": \"as:actor\"}, \"tree:value\": {\"@id\": \"fep34c1:myFollowees\"} },\n    { \"@type\": \"tree:EqualToRelation\", \"tree:path\": [{\"@id\": \"as:to\"}, {\"@id\": \"as:cc\"}], \"tree:value\": {\"@id\": \"https://www.w3.org/ns/activitystreams#Public\"} }\n  ]\n}\n

Semantics: rdf:type = as:Create AND as:actor IN myFollowees AND (as:to = as:Public OR as:cc = as:Public)

"},{"location":"fep/34c1/#7-response-format","title":"7. Response Format","text":"

The response is an OrderedCollectionPage with the filtered items. Pagination MUST use as:next and as:prev (standard ActivityStreams pagination). Servers MAY additionally include tree:relation entries to provide boundary values for TREE-aware clients.

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/tree\",\n    { \"fep34c1\": \"https://w3id.org/fep/34c1#\" }\n  ],\n  \"@type\": \"OrderedCollectionPage\",\n  \"partOf\": \"https://example.com/ap/actors/alice/inbox\",\n  \"next\": \"https://example.com/ap/actors/alice/inbox/filter?cursor=abc123\",\n  \"prev\": \"https://example.com/ap/actors/alice/inbox/filter?cursor=def456\",\n  \"orderedItems\": [\n    { /* Activity 1 */ },\n    { /* Activity 2 */ }\n  ],\n  \"tree:relation\": [\n    {\n      \"@type\": \"tree:GreaterThanRelation\",\n      \"tree:path\": { \"@id\": \"as:published\" },\n      \"tree:value\": \"2026-01-28T10:00:00Z\",\n      \"tree:node\": \"https://example.com/ap/actors/alice/inbox/filter?cursor=abc123\"\n    }\n  ],\n  \"totalItems\": 42\n}\n
"},{"location":"fep/34c1/#8-pagination","title":"8. Pagination","text":"

Servers MUST provide as:next and as:prev links on OrderedCollectionPage responses for standard ActivityStreams pagination. This ensures compatibility with existing ActivityPub clients.

Servers MAY additionally include tree:relation entries to provide boundary values for TREE-aware clients. Each pagination relation uses three properties from the TREE vocabulary:

For example, a tree:GreaterThanRelation with tree:value of \"2026-01-28T10:00:00Z\" and a tree:node URL means: \"Fetching tree:node will return items with as:published greater than 2026-01-28T10:00:00Z.\"

Pagination directions:

"},{"location":"fep/34c1/#9-authentication","title":"9. Authentication","text":"

The filter endpoint MUST require the same authentication as the collection itself:

"},{"location":"fep/34c1/#security-considerations","title":"Security Considerations","text":""},{"location":"fep/34c1/#query-injection","title":"Query Injection","text":"

Servers MUST validate and sanitize filter requests. In particular:

Recommended allowed tree:path values:

"},{"location":"fep/34c1/#rate-limiting","title":"Rate Limiting","text":"

Filter requests are potentially more expensive than regular GET requests. Servers SHOULD implement rate limiting.

"},{"location":"fep/34c1/#no-arbitrary-sparql-queries","title":"No Arbitrary SPARQL Queries","text":"

This FEP explicitly defines no SPARQL interface. The supported operations are limited to the defined tree:Relation types.

"},{"location":"fep/34c1/#examples","title":"Examples","text":""},{"location":"fep/34c1/#example-1-home-timeline","title":"Example 1: Home Timeline","text":"

Shows content lifecycle activities from followed actors, visible to public or followers.

{\n  \"@context\": {\n    \"tree\": \"https://w3id.org/tree#\",\n    \"fep34c1\": \"https://w3id.org/fep/34c1#\",\n    \"as\": \"https://www.w3.org/ns/activitystreams#\",\n    \"rdf\": \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n  },\n  \"@type\": \"fep34c1:FilterRequest\",\n  \"tree:relation\": [\n    {\n      \"@type\": \"tree:EqualToRelation\",\n      \"tree:path\": { \"@id\": \"rdf:type\" },\n      \"tree:value\": [\n        { \"@id\": \"as:Create\" },\n        { \"@id\": \"as:Update\" },\n        { \"@id\": \"as:Delete\" },\n        { \"@id\": \"as:Announce\" }\n      ]\n    },\n    {\n      \"@type\": \"tree:EqualToRelation\",\n      \"tree:path\": { \"@id\": \"as:actor\" },\n      \"tree:value\": { \"@id\": \"fep34c1:myFollowees\" }\n    },\n    {\n      \"@type\": \"tree:EqualToRelation\",\n      \"tree:path\": [{ \"@id\": \"as:to\" }, { \"@id\": \"as:cc\" }],\n      \"tree:value\": [\n        { \"@id\": \"https://www.w3.org/ns/activitystreams#Public\" },\n        { \"@id\": \"fep34c1:myAudience\" }\n      ]\n    }\n  ],\n  \"fep34c1:pageSize\": 20\n}\n
"},{"location":"fep/34c1/#example-2-mentions","title":"Example 2: Mentions","text":"

Shows activities where the actor is directly addressed via as:to or as:cc (public mentions only, excluding DMs).

{\n  \"@context\": {\n    \"tree\": \"https://w3id.org/tree#\",\n    \"fep34c1\": \"https://w3id.org/fep/34c1#\",\n    \"as\": \"https://www.w3.org/ns/activitystreams#\",\n    \"rdf\": \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n  },\n  \"@type\": \"fep34c1:FilterRequest\",\n  \"tree:relation\": [\n    {\n      \"@type\": \"tree:EqualToRelation\",\n      \"tree:path\": { \"@id\": \"rdf:type\" },\n      \"tree:value\": [{ \"@id\": \"as:Create\" }]\n    },\n    {\n      \"@type\": \"tree:EqualToRelation\",\n      \"tree:path\": [{ \"@id\": \"as:to\" }, { \"@id\": \"as:cc\" }],\n      \"tree:value\": { \"@id\": \"https://example.com/ap/actors/alice\" }\n    },\n    {\n      \"@type\": \"tree:EqualToRelation\",\n      \"tree:path\": [{ \"@id\": \"as:to\" }, { \"@id\": \"as:cc\" }],\n      \"tree:value\": { \"@id\": \"https://www.w3.org/ns/activitystreams#Public\" }\n    }\n  ]\n}\n
"},{"location":"fep/34c1/#example-3-private-messages","title":"Example 3: Private Messages","text":"

Shows activities not addressed to as:Public (neither in as:to nor as:cc). This includes direct messages, followers-only posts, and group messages.

{\n  \"@context\": {\n    \"tree\": \"https://w3id.org/tree#\",\n    \"fep34c1\": \"https://w3id.org/fep/34c1#\",\n    \"as\": \"https://www.w3.org/ns/activitystreams#\",\n    \"rdf\": \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n  },\n  \"@type\": \"fep34c1:FilterRequest\",\n  \"tree:relation\": [\n    {\n      \"@type\": \"tree:EqualToRelation\",\n      \"tree:path\": { \"@id\": \"rdf:type\" },\n      \"tree:value\": [{ \"@id\": \"as:Create\" }]\n    },\n    {\n      \"@type\": \"tree:NotEqualToRelation\",\n      \"tree:path\": { \"@id\": \"as:to\" },\n      \"tree:value\": { \"@id\": \"https://www.w3.org/ns/activitystreams#Public\" }\n    },\n    {\n      \"@type\": \"tree:NotEqualToRelation\",\n      \"tree:path\": { \"@id\": \"as:cc\" },\n      \"tree:value\": { \"@id\": \"https://www.w3.org/ns/activitystreams#Public\" }\n    }\n  ]\n}\n
"},{"location":"fep/34c1/#example-4-activities-since-timestamp","title":"Example 4: Activities Since Timestamp","text":"
{\n  \"@context\": {\n    \"tree\": \"https://w3id.org/tree#\",\n    \"fep34c1\": \"https://w3id.org/fep/34c1#\",\n    \"as\": \"https://www.w3.org/ns/activitystreams#\",\n    \"xsd\": \"http://www.w3.org/2001/XMLSchema#\"\n  },\n  \"@type\": \"fep34c1:FilterRequest\",\n  \"tree:relation\": [\n    {\n      \"@type\": \"tree:GreaterThanRelation\",\n      \"tree:path\": { \"@id\": \"as:published\" },\n      \"tree:value\": {\n        \"@value\": \"2026-01-27T00:00:00Z\",\n        \"@type\": \"xsd:dateTime\"\n      }\n    }\n  ]\n}\n
"},{"location":"fep/34c1/#example-5-posts-from-a-specific-instance","title":"Example 5: Posts from a Specific Instance","text":"
{\n  \"@context\": {\n    \"tree\": \"https://w3id.org/tree#\",\n    \"fep34c1\": \"https://w3id.org/fep/34c1#\",\n    \"as\": \"https://www.w3.org/ns/activitystreams#\"\n  },\n  \"@type\": \"fep34c1:FilterRequest\",\n  \"tree:relation\": [\n    {\n      \"@type\": \"tree:PrefixRelation\",\n      \"tree:path\": { \"@id\": \"as:actor\" },\n      \"tree:value\": \"https://mastodon.social/\"\n    }\n  ]\n}\n
"},{"location":"fep/34c1/#implementation-notes","title":"Implementation Notes","text":""},{"location":"fep/34c1/#for-servers","title":"For Servers","text":"
  1. Special placeholder values (fep34c1:myFollowees, fep34c1:myAudience) MUST be resolved at query time before evaluating the filter (see Section 4).

  2. Allowed tree:path values SHOULD be restricted to a known set of ActivityStreams properties (see Security Considerations).

  3. For multi-path with EqualToRelation, the semantics are UNION (match if any path matches). For NotEqualToRelation, the semantics are ALL (none of the paths may match).

"},{"location":"fep/34c1/#for-clients","title":"For Clients","text":"
  1. Clients SHOULD check if tree:search is present in the collection
  2. If not present: fall back to client-side filtering
  3. Filter requests SHOULD be cached (ETag/If-None-Match)
  4. Clients SHOULD expect 405 Method Not Allowed if the server does not support filters
"},{"location":"fep/34c1/#backwards-compatibility","title":"Backwards Compatibility","text":"

This FEP is fully backwards compatible:

"},{"location":"fep/34c1/#implementations","title":"Implementations","text":""},{"location":"fep/34c1/#references","title":"References","text":""},{"location":"fep/34c1/#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/34ec/","title":"FEP-34ec: Notification Collection Endpoint","text":"Authors Status Date received Tracking issue Discussions Repository Fred Hauschel @naturzukunft2026@mastodon.social DRAFT 2026-03-15 #782 Discussions codeberg"},{"location":"fep/34ec/#summary","title":"Summary","text":"

This FEP defines a standardized notification collection for ActivityPub actors. A new notifications property under endpoints (ActivityPub \u00a75.7) provides an OrderedCollection containing references to activities that the server deems notification-worthy. Unlike the inbox, which serves as the delivery channel for all incoming activities, the notification collection is a curated subset \u2014 engagement-oriented events such as likes, mentions, and boosts. Dismissal of notifications uses the standard Remove activity (ActivityPub \u00a77.5). Batch dismissal is supported via FEP-db70 (RemoveAll) with optional FEP-34c1 filtering.

"},{"location":"fep/34ec/#motivation","title":"Motivation","text":"

ActivityPub defines an inbox as the primary collection for incoming activities. However, clients need to distinguish between content-oriented activities (home feed) and engagement-oriented events (notifications) \u2014 a separation that SWICG #21 has long called for.

Today, every Fediverse software (Mastodon, Pleroma, GoToSocial, Misskey) implements notifications as a proprietary REST API without interoperability. There is no standardized ActivityPub endpoint for notifications.

This FEP closes this gap by defining: - A standardized endpoint under endpoints.notifications - A server-curated OrderedCollection of notification-worthy activities - Dismissal via standard Remove (ActivityPub \u00a77.5), batch dismissal via FEP-db70 - Optional filtering via FEP-34c1

No new types or vocabulary beyond the notifications endpoint property are introduced. The collection holds references to existing activities \u2014 the activity types themselves provide categorization.

"},{"location":"fep/34ec/#specification","title":"Specification","text":""},{"location":"fep/34ec/#1-notification-endpoint-on-the-actor","title":"1. Notification Endpoint on the Actor","text":"

A conforming server MUST provide the notifications property under endpoints in the actor object:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/34ec\"\n  ],\n  \"type\": \"Person\",\n  \"id\": \"https://example.com/actors/bob\",\n  \"endpoints\": {\n    \"sharedInbox\": \"https://example.com/inbox\",\n    \"notifications\": \"https://example.com/actors/bob/notifications\"\n  }\n}\n

The notifications property points to an OrderedCollection sorted by server receive time in descending order (newest first).

"},{"location":"fep/34ec/#2-collection-contents","title":"2. Collection Contents","text":"

The notification collection contains references to activities that the server considers notification-worthy for the actor. These are typically engagement-oriented events:

Activity Type Typical Condition Like Object is owned by the actor Announce Object is owned by the actor Create Actor is mentioned (to/cc/tag) Follow Actor is the follow target Update Object is owned by or observed by the actor

This table is non-normative. Servers MAY include any activity type and SHOULD apply their own criteria for what constitutes a notification.

The activities referenced in the notification collection also remain in the actor's inbox. The notification collection is a view, not a separate delivery mechanism.

"},{"location":"fep/34ec/#3-collection-semantics","title":"3. Collection Semantics","text":"
{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/34ec\"\n  ],\n  \"type\": \"OrderedCollection\",\n  \"id\": \"https://example.com/actors/bob/notifications\",\n  \"totalItems\": 3,\n  \"orderedItems\": [\n    \"https://alice.example/activities/like-123\",\n    \"https://carol.example/activities/create-456\",\n    \"https://dave.example/activities/announce-789\"\n  ]\n}\n

Servers MAY inline the full activity objects instead of providing only IRIs. When inlining, the standard Activity Streams representation is used \u2014 no wrapper type is needed:

{\n  \"type\": \"OrderedCollection\",\n  \"id\": \"https://example.com/actors/bob/notifications\",\n  \"totalItems\": 2,\n  \"orderedItems\": [\n    {\n      \"type\": \"Like\",\n      \"id\": \"https://alice.example/activities/like-123\",\n      \"actor\": \"https://alice.example/actors/alice\",\n      \"object\": \"https://example.com/posts/post-1\",\n      \"published\": \"2026-02-24T10:00:00Z\"\n    },\n    {\n      \"type\": \"Create\",\n      \"id\": \"https://carol.example/activities/create-456\",\n      \"actor\": \"https://carol.example/actors/carol\",\n      \"object\": {\n        \"type\": \"Note\",\n        \"content\": \"Hey @bob, check this out!\"\n      },\n      \"published\": \"2026-02-24T09:30:00Z\"\n    }\n  ]\n}\n
"},{"location":"fep/34ec/#4-pagination","title":"4. Pagination","text":"

The collection SHOULD support OrderedCollectionPage pagination. Since the collection only contains pending notifications, it typically remains small. Pagination becomes relevant only with larger volumes.

{\n  \"type\": \"OrderedCollection\",\n  \"id\": \"https://example.com/actors/bob/notifications\",\n  \"totalItems\": 150,\n  \"first\": \"https://example.com/actors/bob/notifications?page=1\"\n}\n
"},{"location":"fep/34ec/#5-c2s-operations","title":"5. C2S Operations","text":""},{"location":"fep/34ec/#51-dismiss-a-single-notification-remove","title":"5.1 Dismiss a Single Notification (Remove)","text":"

A client dismisses a notification by posting a Remove activity to the actor's outbox, as defined in ActivityPub \u00a76.11:

{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"type\": \"Remove\",\n  \"actor\": \"https://example.com/actors/bob\",\n  \"object\": \"https://alice.example/activities/like-123\",\n  \"target\": \"https://example.com/actors/bob/notifications\"\n}\n

The server MUST remove the activity reference from the notification collection. The activity itself MUST NOT be deleted from the inbox.

"},{"location":"fep/34ec/#52-batch-dismiss-removeall","title":"5.2 Batch Dismiss (RemoveAll)","text":"

Batch dismissal is supported via FEP-db70 (RemoveAll). An optional FEP-34c1 filter can be provided to dismiss only matching notifications.

Dismiss all notifications (\"mark all as read\"):

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/db70\"\n  ],\n  \"type\": \"RemoveAll\",\n  \"actor\": \"https://example.com/actors/bob\",\n  \"target\": \"https://example.com/actors/bob/notifications\"\n}\n

Dismiss all notifications of a specific type (with FEP-34c1 filter):

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/db70\",\n    \"https://w3id.org/fep/34c1\",\n    \"https://w3id.org/tree\"\n  ],\n  \"type\": \"RemoveAll\",\n  \"actor\": \"https://example.com/actors/bob\",\n  \"target\": \"https://example.com/actors/bob/notifications\",\n  \"filter\": {\n    \"type\": \"FilterRequest\",\n    \"relation\": [\n      {\n        \"type\": \"EqualToRelation\",\n        \"path\": { \"@id\": \"as:type\" },\n        \"value\": { \"@id\": \"as:Like\" }\n      }\n    ]\n  }\n}\n

Dismiss all notifications older than a given date:

{\n  \"type\": \"RemoveAll\",\n  \"actor\": \"https://example.com/actors/bob\",\n  \"target\": \"https://example.com/actors/bob/notifications\",\n  \"filter\": {\n    \"type\": \"FilterRequest\",\n    \"relation\": [\n      {\n        \"type\": \"LessThanRelation\",\n        \"path\": { \"@id\": \"as:published\" },\n        \"value\": { \"@value\": \"2026-02-17T00:00:00Z\", \"@type\": \"xsd:dateTime\" }\n      }\n    ]\n  }\n}\n

The server MUST remove all activity references matching the filter from the notification collection. Without a filter, the server MUST remove all activity references.

"},{"location":"fep/34ec/#6-collection-filtering-read","title":"6. Collection Filtering (Read)","text":"

Conforming servers SHOULD support FEP-34c1 filtering for the notification collection. The activity type SHOULD be accepted as a tree:path for filtering.

The server signals filter support via tree:search in the collection:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/34ec\",\n    \"https://w3id.org/fep/34c1\",\n    \"https://w3id.org/tree\"\n  ],\n  \"type\": \"OrderedCollection\",\n  \"id\": \"https://example.com/actors/bob/notifications\",\n  \"totalItems\": 25,\n  \"tree:search\": {\n    \"type\": \"FilterEndpoint\",\n    \"template\": \"https://example.com/actors/bob/notifications/filter\"\n  }\n}\n

A client retrieving only Like notifications sends a POST to the filter endpoint:

Request: POST https://example.com/actors/bob/notifications/filter

{\n  \"@context\": [\n    \"https://w3id.org/fep/34c1\",\n    \"https://w3id.org/tree\"\n  ],\n  \"type\": \"FilterRequest\",\n  \"relation\": [\n    {\n      \"type\": \"EqualToRelation\",\n      \"path\": { \"@id\": \"as:type\" },\n      \"value\": { \"@id\": \"as:Like\" }\n    }\n  ],\n  \"pageSize\": 20\n}\n

Response:

{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"type\": \"OrderedCollectionPage\",\n  \"partOf\": \"https://example.com/actors/bob/notifications\",\n  \"totalItems\": 5,\n  \"orderedItems\": [\n    {\n      \"type\": \"Like\",\n      \"id\": \"https://alice.example/activities/like-123\",\n      \"actor\": \"https://alice.example/actors/alice\",\n      \"object\": \"https://example.com/posts/post-1\",\n      \"published\": \"2026-02-24T10:00:00Z\"\n    }\n  ]\n}\n
"},{"location":"fep/34ec/#7-authorization","title":"7. Authorization","text":"

The notification collection MUST only be accessible to the authenticated actor. Unauthenticated requests MUST be rejected with 401 Unauthorized.

"},{"location":"fep/34ec/#security-considerations","title":"Security Considerations","text":""},{"location":"fep/34ec/#conformance","title":"Conformance","text":"

A conforming server MUST: - Provide notifications under endpoints in the actor object - Populate the notification collection with references to notification-worthy activities - Process Remove activities targeting the notification collection by removing the reference - NOT delete the underlying activity from the inbox when removing from the notification collection

A conforming server SHOULD: - Support OrderedCollectionPage pagination for larger volumes - Support FEP-34c1 filtering for the notification collection - Accept activity type as a tree:path in filters - Support FEP-db70 RemoveAll for batch dismissal

"},{"location":"fep/34ec/#implementations","title":"Implementations","text":""},{"location":"fep/34ec/#references","title":"References","text":""},{"location":"fep/34ec/#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/35b7/","title":"FEP-35b7: Fediverse Servers, Instances, and Tenants","text":"Authors Status Type Date received Tracking issue Discussions Repository Steve Bate svc-fep@stevebate.net DRAFT informational 2026-04-22 #832 Discussions codeberg"},{"location":"fep/35b7/#summary","title":"Summary","text":"

This proposal defines terminology related to servers, instances, and tenants in a Fediverse context. The goal is to provide consistent vocabulary for specifications, implementations, and documentation across projects.

"},{"location":"fep/35b7/#motivation","title":"Motivation","text":"

Fediverse discussions use overlapping but sometimes inconsistent terminology when describing software, deployments, and administrative boundaries. In particular, the words \u201cserver\u201d and \u201cinstance\u201d are often used interchangeably.

The terminology issue becomes even more clear for multi-tenant server implementations and instances. As more projects implement multi-tenant architectures, clear terminology becomes important for:

This document proposes terminology that other FEPs and implementations may reference.

"},{"location":"fep/35b7/#definitions","title":"Definitions","text":""},{"location":"fep/35b7/#server-implementation","title":"Server implementation","text":"

A server implementation is a software codebase that implements one or more Fediverse-related protocols (such as ActivityPub). This software must be deployed on computational infrastructure to participate in the Fediverse.

Characteristics:

Example Implementations:

The word server may also refer to computational infrastructure (physical or virtual hardware, containers, etc.) in some contexts. This is not how the word is used in this proposal, which only refers to server software.

"},{"location":"fep/35b7/#server-instance","title":"Server instance","text":"

A server instance is a specific deployment of a server implementation, including its running processes, configuration, and associated persistent storage, operated by a defined administrative authority.

Characteristics:

Examples:

In common Fediverse usage, \u201cinstance\u201d often implicitly refers to a server instance; this document uses the explicit term \u201cserver instance\u201d for clarity.

"},{"location":"fep/35b7/#tenant","title":"Tenant","text":"

A tenant in a Fediverse context is a logically and administratively distinct group of actors and related data that is served by a server instance and for which the server provides isolated configuration, policies, and data separation.

This definition adapts the general notion of multitenancy where a single software instance serves multiple tenants while isolating their data and configuration to the specific case of Fediverse services.

Characteristics:

Examples:

Related concepts:

"},{"location":"fep/35b7/#relationship-between-terms","title":"Relationship Between Terms","text":"
+-----------------------+\n| Server implementation |\n+-----------------------+\n| software codebase     |\n| release artifacts     |\n+-----------------------+\n         |\n         | 0..*\n         v\n+-----------------------+\n|    Server instance    |\n+-----------------------+\n| running deployment    |\n| config + storage      |\n| admin boundary        |\n+-----------------------+\n    |                 \\\n    |                  \\\n    |                   \\\n    v                    v\n+----------------+  +----------------+\n| Single tenant  |  | Multi-tenant   |\n| deployment     |  | deployment     |\n+----------------+  +----------------+\n     \\              /\n      \\            /\n       \\          /\n        \\ 1      / 1..*\n         v      v\n       +----------------+\n       |     Tenant     |\n       +----------------+\n       | isolated data  |\n       | isolated policy|\n       | admin boundary |\n       +----------------+\n
"},{"location":"fep/35b7/#scope-and-non-goals","title":"Scope and Non-Goals","text":"

This document:

"},{"location":"fep/35b7/#references","title":"References","text":""},{"location":"fep/35b7/#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/37f2/","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/@bengo DRAFT 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:

"},{"location":"fep/37f2/#w3c-community-group-process","title":"W3C Community Group Process","text":"

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":""},{"location":"fep/37f2/#editorial-notes","title":"Editorial Notes","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/3ab2/","title":"FEP-3ab2: ActivityPub Event Streaming API","text":"Authors Status Date received Tracking issue Discussions Repository Steve Bate svc-fep@stevebate.net DRAFT 2026-03-14 #778 Discussions codeberg"},{"location":"fep/3ab2/#motivation","title":"Motivation","text":"

The ActivityPub specification defines a server-to-server federation protocol but does not specify how a client application (e.g. a Web front-end or mobile app) receives real-time notifications from an ActivityPub server. This FEP proposes a straightforward, standards-aligned approach using HTTP/1.1 Server-Sent Events.

Goals: - Reuse existing HTTP authentication. - Efficiently use SSE streaming resources - Provide a discoverable, RESTful control API for managing SSE sessions.

"},{"location":"fep/3ab2/#summary","title":"Summary","text":"

This FEP specifies a lightweight, cookie-authenticated Server-Sent Events (SSE) streaming API that ActivityPub server implementations can expose to their authenticated clients. The API provides:

  1. A session-control sub-API for issuing and revoking short-lived, singoe-use streaming tickets, and for managing per-user topic subscriptions.
  2. A stream endpoint that delivers a multiplexed, real-time event feed for all topics the authenticated to which the user is subscribed.

The design intentionally separates authentication (handled by the server's existing mechanism, e.g. OAuth 2.0, session cookies, or HTTP Basic) from streaming authorization (a short-lived ticket stored in an HttpOnly cookie), so that the SSE connection never carries user credentials.

This proposal addresses two issues related to SSE event streaming in an ActivityPub context.

"},{"location":"fep/3ab2/#terminology","title":"Terminology","text":"Term Definition Actor A standard ActivityPub actor Principal The authenticated identity (URI of the Actor) making a request. Ticket A short-lived, randomly-generated opaque token that authorizes one SSE stream connection. Topic An opaque string naming a channel of events (e.g. notifications, inbox, timeline:home). SSE Session The lifecycle from ticket issuance through stream connection to ticket revocation or expiry.

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/3ab2/#endpoint-discovery","title":"Endpoint Discovery","text":"

The streaming control endpoint is exposed using the ActivityPub Actor endpoints property using the streamingControl endpoint property.

{\n  \"@context\": [\n    \"https://w3id.org/fep/3ab2\",\n    \"https://www.w3.org/ns/activitystreams\"\n  ],\n  \"type\": \"Person\",\n  \"id\": \"https://server.example/actor/1\",\n  ...\n  \"endpoints\": [\n    \"streamingControl\": \"https://server.example/streaming/control\"\n  ]\n}\n
"},{"location":"fep/3ab2/#topics","title":"Topics","text":"

Topic represented as multiple text segments separated by the \"/\" character.

"},{"location":"fep/3ab2/#topic-wildcards","title":"Topic Wildcards","text":"

For subscriptions, topics MAY contain wildcards if the server supports them. The standard MQTT wildcards are used for this purpose. A server can advertise wildcard support in the Create Session response via the wildcard_support field.

Wildcard Description Example + Matches a single topic segment foo/+/baz # Matches zero or more segments at the end of the pattern foo/#

When publishing, the topic MUST use URL escaping for any wildcard characters in the topic. Subscribers MUST also escape non-wildcard '+' and '#' characters in topic subscriptions.

"},{"location":"fep/3ab2/#activitypub-topics","title":"ActivityPub topics","text":"

For ActivityPub entities, topics will correspond to ActivityPub HTTP/HTTPS URIs. To construct a topic from a URI, the following algorithm is used.

  1. Remove the URL scheme.
  2. The URL authority (domain and optional port) is the first segment of the topic.
  3. The URL path becomes \"/\"-separated path segments
  4. If a URL fragment is present this becomes the final segment.

Topic wildcard characters in topic segments MUST be URL-escaped.

Examples

URL Topic https://server.example/note/1 server.example/note/1 https://server.example:1000/actor#xyz server.example:1000/actor/xyz https://server.example:1000/actor+123#xyz server.example:1000/actor%2B123/xyz"},{"location":"fep/3ab2/#session-management","title":"Session Management","text":""},{"location":"fep/3ab2/#create-session-post-streamingcontrol","title":"Create Session \u2014 POST {streamingControl}","text":"

Requires: authenticated Principal.

The server:

  1. Generates a cryptographically random ticket.
  2. Records SSE session state (ticket, Principal, origin, etc.)
  3. Sets a Set-Cookie response header carrying the ticket value with the configured path, SameSite, Secure, and HttpOnly attributes.
  4. Returns 201 Created with a JSON body.

Server Response body:

Field Type Description subscriptions_url string Required. Absolute URL of the subscriptions endpoint. Clients MUST use this value for all subscription management requests in this session. stream_url string Required. Absolute URL of the SSE stream endpoint. Clients MUST use this value to open the event stream. expires_at string ISO 8601 UTC timestamp of ticket expiry wildcard_support bool Indicates whether the server supports topic wildcards or not. Defaults is true if not provided.

Servers MUST include subscriptions_url and stream_url in every successful 201 Created response to this endpoint. Clients MUST treat these URLs as opaque and MUST NOT attempt to derive or construct them from the SSE prefix or any other path convention.

Example

{\n  \"subscriptions_url\": \"https://server.example/sse/control/subscriptions\",\n  \"stream_url\": \"https://server.example/sse/stream\",\n  \"expires_at\": \"2001-03-10T12:05:00Z\",\n  \"wildcard_support\": true\n}\n

Cookie attributes (RECOMMENDED defaults):

Attribute Recommended value Path Scoped to the implementation's streaming API path SameSite Lax Secure true in production HttpOnly Server MAY set to false to allow JavaScript access Max-Age Cookie expiry age

The ticket MUST expire after a server-configured TTL. Clients MUST call POST /control again to renew before opening a new stream.

"},{"location":"fep/3ab2/#revoke-session-delete-streamingcontrol","title":"Revoke Session \u2014 DELETE {streamingControl}","text":"

Requires: authenticated Principal. The ticket cookie is read if present.

The server: 1. Invalidates the ticket. 2. Sends a Set-Cookie header that clears the cookie (Max-Age=0). 3. Returns 204 No Content if successfull.

Client implementations SHOULD also close any active SSE connections associated with the revoked ticket.

"},{"location":"fep/3ab2/#subscription-management","title":"Subscription Management","text":"

All subscription endpoints require both: - A valid authentication credential. - A valid, non-expired ticket cookie associated with the authenticated Principal.

Violating either condition MUST result in 401 Unauthorized.

"},{"location":"fep/3ab2/#list-subscriptions-get-subscriptions_url","title":"List Subscriptions \u2014 GET {subscriptions_url}","text":"

Response:

{\n  \"topics\": [\"notifications\", \"timeline:home\"]\n}\n
Field Type Description topics string\u00a0array The full set of subscribed topics. An empty array indicates no active subscriptions."},{"location":"fep/3ab2/#add-subscription-post-subscriptions_url","title":"Add Subscription \u2014 POST {subscriptions_url}","text":"

Request body:

{\n  \"topics\": [\n    \"remote-server.example/note/abcd\"\n    \"server.example/note/#\",\n  ]\n}\n
Field Type Required Description topics string\u00a0array yes The topic identifiers or patterns

Response:

{\n  \"topics\": [\n    \"remote-server.example/note/abcd\"\n    \"server.example/note/#\",\n    \"server.example/another-topic\"\n  ]\n}\n
Field Type Description topics string\u00a0array The full set of subscribed topics. The array reflects the full set of subscriptions after the operation. Adding a topic that is already subscribed is idempotent and MUST NOT return an error."},{"location":"fep/3ab2/#remove-subscription-delete-subscriptions_urltopictopic","title":"Remove Subscription \u2014 DELETE {subscriptions_url}?topic={topic}","text":"

The topic query parameter is required. Removing a non-existent topic MUST be idempotent (no error).

Response:

"},{"location":"fep/3ab2/#sse-stream-endpoint","title":"SSE Stream Endpoint","text":""},{"location":"fep/3ab2/#opening-the-stream-get-stream_url","title":"Opening the Stream \u2014 GET {stream_url}","text":"

Requires: - A valid authentication credential. - A valid, non-expired ticket cookie whose associated identity matches the Principal.

The client MUST send Accept: text/event-stream.

On success the server: 1. Upgrades the connection to an SSE stream (Content-Type: text/event-stream). 2. Sets Cache-Control: no-cache and (if behind a reverse proxy) X-Accel-Buffering: no. 3. Begins delivering ServerSentEvent frames for all topics the user is currently subscribed to. 4. Keeps the connection open until the client disconnects, the ticket expires, the session is revoked, or the server restarts.

Only one active stream per Principal SHOULD be supported at a time; if a second stream is opened the server MAY close the first.

The ticket is single-use. The server MUST only create one stream per ticket.

"},{"location":"fep/3ab2/#activitypub-sse-event-format","title":"ActivityPub SSE Event Format","text":"

Each event follows the W3C EventSource wire format:

id: <event-id>\\n\nevent: activitypub\\n\ndata: <JSON-object>\\n\n\\n\n
SSE field Value id The id field of the payload object, or a server-generated UUID hex event activitypub (implementations MAY define additional named event types) data A JSON-serialized Activitypub entity

Example ActivityPub Event Payload (data)

{\n  \"@context\":\"https://www.w3.org/ns/activitystreams\",\n  \"id\": \"https://example.com/activities/abc123\",\n  \"type\": \"Create\",\n  \"published\": \"2026-03-10T11:00:00Z\",\n  \"object\": { ... }\n}\n
"},{"location":"fep/3ab2/#heartbeats","title":"Heartbeats","text":"

Servers SHOULD send periodic SSE heartbeat events (event: heartbeat) at regular intervals (RECOMMENDED: every 15\u201330 seconds) to prevent proxy timeouts and help clients detect dead connections. The id and data fields are optional in these messages.

"},{"location":"fep/3ab2/#security-considerations","title":"Security Considerations","text":""},{"location":"fep/3ab2/#ticket-security","title":"Ticket Security","text":""},{"location":"fep/3ab2/#cookie-security","title":"Cookie Security","text":""},{"location":"fep/3ab2/#authorization","title":"Authorization","text":""},{"location":"fep/3ab2/#topic-namespace","title":"Topic Namespace","text":""},{"location":"fep/3ab2/#transport-security","title":"Transport Security","text":""},{"location":"fep/3ab2/#privacy-considerations","title":"Privacy Considerations","text":"

The SSE stream may carry sensitive information (notifications, timeline events). Implementations MUST: - Restrict stream access to the owning Principal. - Limit streaming events to content that the Principal is authorized to view. - Not include events from topics the Principal has not explicitly subscribed to. - Consider data minimization: deliver only the fields necessary for the client's use case.

"},{"location":"fep/3ab2/#implementations","title":"Implementations","text":""},{"location":"fep/3ab2/#alternative-approaches","title":"Alternative Approaches","text":"

Another common technique for implementing event stream is to use Websockets. SSE has several advantages over WebSockets for streaming purposes.

"},{"location":"fep/3ab2/#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/3b86/","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.

"},{"location":"fep/3b86/#1-requirements","title":"1. Requirements","text":"

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.

"},{"location":"fep/3b86/#action-redirecting-to-a-url","title":"Action: Redirecting to a URL","text":"

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)

"},{"location":"fep/3b86/#35-endpoint-expectations","title":"3.5 Endpoint Expectations","text":"

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.

"},{"location":"fep/3b86/#36-publishing-intents-for-application-actors","title":"3.6 Publishing Intents for @application Actors","text":"

For most implementations, Activity Intent endpoints will be identical for every user account on the server. For instance, Mastodon's /share endpoint works for every account, and only varies based on the authentication headers passed to the server.

If a home server uses the same intent locations for every actor, home servers SHOULD include these values in the WebFinger results for the @application actor, in addition to each individual actor.

Doing this improves compatibility across the Fediverse, and enables remote servers to only request a visitor's server name (example.social) instead of their full WebFinger handle (@username@example.social)

"},{"location":"fep/3b86/#4-standard-intent-definitions","title":"4. Standard Intent Definitions","text":""},{"location":"fep/3b86/#41-accept-intent","title":"4.1. Accept Intent","text":"

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":""},{"location":"fep/3b86/#412-example","title":"4.1.2. Example","text":"
{\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":""},{"location":"fep/3b86/#422-example","title":"4.2.2. Example","text":"
{\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":""},{"location":"fep/3b86/#432-example","title":"4.3.2. Example","text":"
{\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":"fep/3b86/#442-example","title":"4.4.2. Example","text":"
{\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":""},{"location":"fep/3b86/#452-example","title":"4.5.2. Example","text":"
{\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 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":"

Discussion and Threading * {audience} - (optional) The ID of the audience to pre-populate into the created object. * {context} - (optional) The ID of the context that this object belongs to. * {inReplyTo} - (optional)The ID of the ActivityStreams Document that this object is inReplyTo.

Other 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.

"},{"location":"fep/3b86/#462-recommendations","title":"4.6.2 Recommendations","text":"

In ActivityPub, the Create activity has many uses, such as creating new posts, replying to existing posts, and even sharing external web content back into the Fediverse. All of these actions can all be done using various tokens present in the Create intent template.

To maximize compatibility, home servers SHOULD include the following tokens in their Create template strings.

Clients (and client libraries) can determine if a home server supports any of these use cases by inspecting the template strings for the corresponding tokens. For instance, a remote server can confirm that the {inReplyTo} token is present in the Create intent template. If it is present, then the remote server can display or activate a \"reply to\" link on its content page.

"},{"location":"fep/3b86/#463-share-example","title":"4.6.3. Share Example","text":"
{\n    \"rel\": \"https://w3id.org/fep/3b86/Create\",\n    \"template\": \"https://mastodon.social/share?text={content}\"\n}\n

Several servers implement \"share\" buttons that let users share web page content back into the Fediverse. To do this, they pre-populate the {content} token of the Activity Intent with the URL of the web page to be shared.

"},{"location":"fep/3b86/#464-reply-example","title":"4.6.4 Reply Example","text":"
{\n    \"rel\": \"https://w3id.org/fep/3b86/Create\",\n    \"template\": \"https://server.social/@me/create?inReplyTo={inReplyTo}\"\n}\n

Remote servers can inspect the template string to determine if replies are supported or not. If the template string includes an {inReplyTo} token, then the home server can accept posts that are direct replies to other ActivityPub objects.

"},{"location":"fep/3b86/#465-threaded-discussion-example","title":"4.6.5 Threaded Discussion Example","text":"
{\n    \"rel\": \"https://w3id.org/fep/3b86/Create\",\n    \"template\": \"https://server.social/@me/create?audience={audience}&context={context}\"\n}\n

Threaded discussions such as forums and bulletin boards typically use {audience} and {context} tokens to place content into the correct part of a discussion. Home servers that want their users to participate in threaded discussions SHOULD publish these tokens in the template string. Endpoints can then pre-populate the values into the resulting activity.

"},{"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":""},{"location":"fep/3b86/#472-example","title":"4.7.2. Example","text":"
{\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":""},{"location":"fep/3b86/#482-example","title":"4.8.2. Example","text":"
{\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":""},{"location":"fep/3b86/#492-example","title":"4.9.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4102-example","title":"4.10.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4112-example","title":"4.11.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4122-example","title":"4.12.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4132-example","title":"4.13.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4142-example","title":"4.14.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4152-example","title":"4.15.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4162-example","title":"4.16.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4172-example","title":"4.17.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4182-example","title":"4.18.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4192-example","title":"4.19.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4202-example","title":"4.20.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4212-example","title":"4.21.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4222-example","title":"4.22.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4232-example","title":"4.23.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4242-example","title":"4.24.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4252-example","title":"4.25.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4262-example","title":"4.26.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4272-example","title":"4.27.2. Example","text":"
{\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":""},{"location":"fep/3b86/#4282-example","title":"4.28.2. Example","text":"
{\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.

"},{"location":"fep/3b86/#511-parameters","title":"5.1.1. Parameters","text":""},{"location":"fep/3b86/#512-example","title":"5.1.2. Example","text":"
{\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.

"},{"location":"fep/3b86/#62-openunvalidated-redirects","title":"6.2 Open/Unvalidated Redirects","text":"

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/#63-malicious-template-strings","title":"6.3 Malicious Template Strings","text":"

When generating redirect URLs from Activity Intent template strings, remote clients MUST apply the following validations to the generated URL before allowing users to use that Activity Intent.

If the generated URL fails any of the tests below, the client MUST erase the result and treat the result as if no template URL was provided for that Activity Intent. This prevents malicious servers from accessing data on the user's local machine or local network.

"},{"location":"fep/3b86/#631-https-only","title":"6.3.1. HTTPS Only","text":"

URLs MUST use HTTPS protocol, i.e. URLs MUST begin with the string https://. All other protocols MUST be rejected.

If a URL fails any of this test, the client MUST treat it as if no template URL was provided by the server. This prevents a malicious server from using Activity Intents to execute code (via a javascript: protocol), access data from the user's local machine (via the file:// protocol), an unsecured device (via the http:// protocol), or another unexpected resource.

"},{"location":"fep/3b86/#632-public-network-only","title":"6.3.2. Public Network Only","text":"

URLs MUST point to a public-facing server location, i.e. URLs MUST NOT allow local network addresses. This includes private IPv4 network addresses, private IPv6 network addresses, loopback addresses, localhost, and *local domains.

If a URL fails this test, the client MUST treat it as if no template URL was provided by the server. This prevents malicious home servers from using Activity Intents to access data on a user's local machine (via localhost and loopback addresses) or using the user as a proxy into their local network (via 192.168.X.X and similar local network addresses).

"},{"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:

  1. A user visits a remote server (i.e., any address on the Internet where they do not have an ActivityPub account)
  2. The remote server displays buttons such as \"like\" or \"share\" for a particular document or object.
  3. When the user clicks on one of these links, the remote server checks to see if the user is already recognized. This does not necessarily mean logged in, or authenticated, just whether or not the user has entered their home server credentials before. True authentication on the remote server is not necessary for Activity Intents to work.
    1. If the user is not recognized, then they are prompted to enter their Fediverse ID (such as @benpate@mastodon.social). The remote server can save this information for use in the future (preferably in a private data store, such as localStorage)
    2. If the user is recognized, this means they have already completed step 3.1, and can continue to step 4.
  4. The remote server uses a standard WebFinger query to look up the user's Fediverse ID and searches for Activity Intents supported by the user's home server.
    1. If none are found, the remote server MAY try to substitute fallback values for known server types.
    2. If none are found (and no fallbacks substituted) the remote server SHOULD inform the user that their account is incompatible with the selected feature and halt the workflow.
  5. The remote server replaces values into the designated URI Template and forwards the user to the assigned page on their home server. This initiates the Activity Intent workflow on their home server.
  6. When the user completes the workflow, the home server SHOULD use URL in the on-success parameter to redirect the user back to the correct page on the remote server.
    1. Similarly, if the user cancels the workflow, the home server SHOULD use the URL in the on-cancel parameter to redirect the user back to the correct page on the remote server.
"},{"location":"fep/3b86/#72-fallbacks-for-unpublished-links","title":"7.2. Fallbacks for Unpublished Links","text":"

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.

"},{"location":"fep/3b86/#80-implementations","title":"8.0. Implementations","text":""},{"location":"fep/3b86/#81-home-servers","title":"8.1 Home Servers","text":"

This is a list of \"home servers\" that publish Activity Intent endpoints for users' profiles.

"},{"location":"fep/3b86/#82-remote-servers","title":"8.2 Remote Servers","text":"

Applications that use Activity Intents to create interactive links and buttons that pull users from remote websites back to their home servers.

"},{"location":"fep/3b86/#83-libraries","title":"8.3 Libraries","text":""},{"location":"fep/3b86/#references","title":"References","text":""},{"location":"fep/3b86/#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/400e/","title":"FEP-400e: Publicly-appendable ActivityPub collections","text":"Authors Status Date received Date final Tracking issue Discussions Repository Gregory Klyushnikov activitypub@grishka.me FINAL 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.

"},{"location":"fep/400e/#specifying-collections-in-actors","title":"Specifying collections in actors","text":"

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":"Using target 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:

"},{"location":"fep/400e/#discussion","title":"Discussion","text":"

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.

"},{"location":"fep/400e/#adding-an-object-to-a-collection","title":"Adding an object to a collection","text":"

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:

"},{"location":"fep/400e/#receiving-an-add-activity","title":"Receiving an Add activity","text":"

When an ActivityPub server receives in its inbox a correctly signed Add activity, it SHOULD do the following:

"},{"location":"fep/400e/#example-of-an-add-activity","title":"Example of an 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.

"},{"location":"fep/400e/#moving-an-object-between-collections","title":"Moving an object between collections","text":"

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.

"},{"location":"fep/400e/#security-considerations","title":"Security considerations","text":"

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.

"},{"location":"fep/400e/#implementations","title":"Implementations","text":"

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":""},{"location":"fep/400e/#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/4adb/","title":"FEP-4adb: Dereferencing identifiers with webfinger","text":"Authors Status Date received Tracking issue Discussions Repository Helge helge.krueger@gmail.com DRAFT 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:

  1. Fetch GET https://domain/.well-known/webfinger?resource={URI}.
  2. Determine the link property with \"type\":\"application/activity+json\".
  3. Dereference this object.

We will denote this object by ActivityStreams(URI, domain). There are now two cases to resolve an URI:

  1. The URI determines the domain denoted by domain(URI)
  2. The URI doesn't determine the domain

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.

"},{"location":"fep/4adb/#examples","title":"Examples","text":"

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.

"},{"location":"fep/4adb/#applications-to-the-actor-object","title":"Applications to the Actor Object","text":"

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.

"},{"location":"fep/4adb/#dns-names","title":"DNS Names","text":"

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.

"},{"location":"fep/4adb/#using-didkey","title":"Using did:key","text":"

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":""},{"location":"fep/4adb/#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/4ccd/","title":"FEP-4ccd: Pending Followers Collection and Pending Following Collection","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@prodromou.name DRAFT 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.

"},{"location":"fep/4ccd/#motivation","title":"Motivation","text":"

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.

All terms in the context document use the same namespace, https://purl.archive.org/socialweb/pending#.

"},{"location":"fep/4ccd/#properties","title":"Properties","text":"

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.

"},{"location":"fep/4ccd/#actor-with-pendingfollowers-and-pendingfollowing","title":"Actor with 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.

"},{"location":"fep/4ccd/#pendingfollowers-collection","title":"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.

"},{"location":"fep/4ccd/#pendingfollowing-collection","title":"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.

"},{"location":"fep/4ccd/#processing-requirements","title":"Processing requirements","text":"

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.

"},{"location":"fep/4ccd/#security-considerations","title":"Security considerations","text":"

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.

"},{"location":"fep/4ccd/#implementations","title":"Implementations","text":""},{"location":"fep/4ccd/#references","title":"References","text":""},{"location":"fep/4ccd/#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/4f05/","title":"FEP-4f05: Soft Deletion","text":"Authors Status Date received Tracking issue Discussions Repository Julian Lam julian@nodebb.org, Angus McLeod angus@pavilion.tech DRAFT 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:

The Forums and Threaded Discussions Task Force (ForumWG) has identified a common nomenclature when referring to organized objects in a threaded discussion model.

"},{"location":"fep/4f05/#publishers","title":"Publishers","text":""},{"location":"fep/4f05/#soft-deletion","title":"Soft deletion","text":"

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.

"},{"location":"fep/4f05/#hard-deletion","title":"Hard deletion","text":"

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.

"},{"location":"fep/4f05/#receivers","title":"Receivers","text":"

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.

"},{"location":"fep/4f05/#tombstone","title":"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 a Tombstone","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.

"},{"location":"fep/4f05/#additional-considerations","title":"Additional Considerations","text":""},{"location":"fep/4f05/#update-activity","title":"Update activity","text":"

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.

"},{"location":"fep/4f05/#broad-support-or-lack-thereof","title":"Broad support (or lack thereof)","text":"

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.

"},{"location":"fep/4f05/#implementor-ux","title":"Implementor UX","text":"

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.

"},{"location":"fep/4f05/#implementors","title":"Implementors","text":""},{"location":"fep/4f05/#references","title":"References","text":""},{"location":"fep/4f05/#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/5219/","title":"FEP-5219: Groups and permissions","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT implementation 2026-06-11 #860 Discussions codeberg"},{"location":"fep/5219/#summary","title":"Summary","text":"

This document describes a permission system for ActivityPub groups.

It is inspired by XEP-0045: Multi-User Chat, which defines a comprehensive permission system for XMPP group chats.

"},{"location":"fep/5219/#motivation","title":"Motivation","text":"

The most widely used ActivityPub group implementation is described in FEP-1b12 (Group federation). That document was focused on public groups, and private FEP-1b12 groups were later proposed in Lemmy RFC-0005 (Private Communities). However, the mechanism for controlling user permissions in such groups has not been clearly defined.

This FEP formalizes the mechanisms that are already used in FEP-1b12 groups, and extends them to accommodate new group types, such as semi-private groups and group chats. The proposed permission system can also be used in other contexts, such as when designating server moderators or establishing organizational hierarchies.

"},{"location":"fep/5219/#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/5219/#affiliation","title":"Affiliation","text":"

Affiliation is a long-lived association with a group.

The default set of affiliations includes:

This list is not exhaustive, and implementations MAY support additional affiliations if necessary. It is also possible to have no affiliation with a group.

The group actor itself is considered to have an admin affiliation.

Affiliations can be specified using the affiliations collection attached to a Group actor. It is an ordered collection where each item is a Relationship object that represents a relationship between a user and a group. This object has the following fields:

The affiliations collection is sorted from most privilege to least privilege.

When adding a Relationship for a custom affiliation, a fallback representation SHOULD also be included. This fallback representation MUST specify an affiliation from the default set that has equal or fewer privileges compared to the custom affiliation.

Consumers MUST ignore relationships that represent unsupported affiliations.

The affiliations collection supersedes the collection of moderators defined in FEP-1b12. Consumers MUST read the affiliations collection when both collections are present.

Example of an affiliations collection:

{\n  \"id\": \"https://social.example/group/affiliations\",\n  \"type\": \"OrderedCollection\",\n  \"attributedTo\": \"https://social.example/group\",\n  \"items\": [\n    {\n      \"type\": \"Relationship\",\n      \"subject\": \"https://social.example/alice\",\n      \"relationship\": \"admin\"\n    },\n    {\n      \"type\": \"Relationship\",\n      \"subject\": \"https://social.example/bob\",\n      \"relationship\": \"member\"\n    }\n  ]\n}\n

The collection is modified using Add and Remove activities. They have the following properties:

Example:

{\n  \"id\": \"https://social.example/activities/1\",\n  \"type\": \"Add\",\n  \"object\": {\n    \"type\": \"Relationship\",\n    \"subject\": \"https://social.example/bob\",\n    \"relationship\": \"member\"\n  },\n  \"target\": \"https://social.example/group/affiliations\",\n  \"to\": [\n    \"https://social.example/group/followers\"\n  ]\n}\n
"},{"location":"fep/5219/#participation","title":"Participation","text":"

Participation refers to the ability to read or write to a group.

There are two levels of participation:

The participation status is distinct from affiliation. Affiliations MAY be preserved when users leave and re-join the group.

A group where anyone can participate is a public group.

A group where only members can participate is a private group.

A group which anyone can follow but where only members can participate is a semi-private group.

Group actors MUST have a followers property pointing to the collection of group followers.

Semi-private group actors MUST have a participants property pointing to the collection of group participants. In public and private groups, the collection of participants is identical to the collection of followers.

"},{"location":"fep/5219/#privileges","title":"Privileges","text":"Privilege Activity Admin Moderator Member None Outcast Follow a public group Follow Yes Yes Yes Yes No Participate in a public group - Yes Yes Yes Yes No Follow a semi-private group Follow Yes Yes Yes Yes No Participate in a semi-private group Join Yes Yes Yes No No Follow a private group Join or Follow Yes Yes Yes No No Participate in a private group Join or Follow Yes Yes Yes No No Remove posts of other participants Delete Yes Yes No No No Remove participants Reject Yes Yes No No No Ban participants Block Yes Yes No No No Add or remove members Add or Remove Yes Yes No No No Add or remove moderators Add or Remove Yes No No No No Update group metadata Update Yes No No No No

This list of privileges is not exhaustive. Additional privileges MAY be associated with the affiliations from the default set.

"},{"location":"fep/5219/#activities","title":"Activities","text":"

Group activities (such as FEP-1b12 Announce activities) MUST be addressed to the group's followers collection or to its participants collection.

Recipients of a group activity SHOULD resolve the group's affiliations collection.

All participants are allowed to update and delete their own posts, and to remove themselves from the group. Actions that affect other participants MAY be authorized if the actor has the necessary privileges.

"},{"location":"fep/5219/#references","title":"References","text":""},{"location":"fep/5219/#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/521a/","title":"FEP-521a: Representing actor's public keys","text":"Authors Status Type Date received Date final Tracking issue Discussions Repository silverpill @silverpill@mitra.social FINAL 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.

"},{"location":"fep/521a/#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/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:

"},{"location":"fep/521a/#key-ids","title":"Key IDs","text":"

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.

"},{"location":"fep/521a/#test-vectors","title":"Test vectors","text":"

See fep-521a.feature

"},{"location":"fep/521a/#implementations","title":"Implementations","text":""},{"location":"fep/521a/#references","title":"References","text":""},{"location":"fep/521a/#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/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.

"},{"location":"fep/521a/fep-521a-test-case/#about-this-test","title":"About this Test","text":"

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:

"},{"location":"fep/521a/fep-521a-test-case/#applicability","title":"Applicability","text":"

This test applies directly to the actor input.

"},{"location":"fep/521a/fep-521a-test-case/#test-targets","title":"Test Targets","text":""},{"location":"fep/521a/fep-521a-test-case/#expectations","title":"Expectations","text":"
  1. assertionMethod[x].id - MUST be a string
  2. assertionMethod[x].controller - MUST match the id property, if present, of the assertionMethod array's parent (i.e., the Actor object)
  3. assertionMethod[x].publicKeyMultibase - MUST be a base58btc-encoded and appropriately-prefixed (in this case, beginning with z) expression of a binary public key expression
"},{"location":"fep/521a/fep-521a-test-case/#assumptions","title":"Assumptions","text":""},{"location":"fep/521a/fep-521a-test-case/#1-how-to-determine-whether-actor-object-is-expressing-a-key-in-the-fep-defined-shape","title":"1. How to Determine Whether Actor Object is expressing a key in the FEP-defined shape","text":"

For the purposes of determining whether the input actor is expressing a key in the shape defined by the FEP:

"},{"location":"fep/521a/fep-521a-test-case/#2-property-value-expectations","title":"2. Property value expectations","text":"

For the purposes of determining whether the input actor is expressing 1 or more keys all the following must be true:

"},{"location":"fep/521a/fep-521a-test-case/#test-cases","title":"Test Cases","text":"

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

"},{"location":"fep/521a/fep-521a-test-case/#misshapen-assertionmethod-array","title":"Misshapen assertionMethod Array","text":"

input

actor:

{\n    \"type\": \"Person\",\n    \"inbox\": \"https://example.com/inbox\",\n    \"outbox\": \"https://example.com/outbox\",\n    \"assertionMethod\": \"z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2\"\n}\n

test targets

"},{"location":"fep/521a/fep-521a-test-case/#misshapen-assertionmethod-members","title":"Misshapen assertionMethod Members","text":"

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

"},{"location":"fep/521a/fep-521a-test-case/#malformed-publickeymultibase-value","title":"Malformed publicKeyMultibase Value","text":"

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

"},{"location":"fep/521a/fep-521a-test-case/#valid-actor","title":"Valid Actor","text":"

input

test targets

"},{"location":"fep/521a/fep-521a-test-case/#glossary","title":"Glossary","text":""},{"location":"fep/521a/fep-521a-test-case/#outcome","title":"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:

"},{"location":"fep/521a/fep-521a-test-case/#requirements-mapping","title":"Requirements Mapping","text":""},{"location":"fep/521a/fep-521a-test-case/#change-log","title":"Change Log","text":""},{"location":"fep/521a/fep-521a-test-case/#issues-list","title":"Issues List","text":""},{"location":"fep/5624/","title":"FEP-5624: Per-object reply control policies","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Claire claire.fep-1d7d@sitedethib.com WITHDRAWN 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.

"},{"location":"fep/5624/#receiving-and-accepting-a-reply","title":"Receiving and accepting a reply","text":"

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.

"},{"location":"fep/5624/#example-approvereply-activity","title":"Example 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.

"},{"location":"fep/5624/#example-reply-object-with-replyapproval","title":"Example reply object with 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.

"},{"location":"fep/5624/#revoking-a-previously-accepted-reply","title":"Revoking a previously-accepted reply","text":"

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.

"},{"location":"fep/5624/#handling-a-revocation","title":"Handling a revocation","text":"

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

"},{"location":"fep/5624/#deployment-considerations","title":"Deployment considerations","text":"

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:

"},{"location":"fep/5624/#implementations","title":"Implementations","text":"

None so far.

"},{"location":"fep/5624/#references","title":"References","text":""},{"location":"fep/5624/#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/5711/","title":"FEP-5711: Inverse Properties for Collections","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.org DRAFT 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":""},{"location":"fep/5711/#specification","title":"Specification","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":"

Defines 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":"

Defines 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":"

Defines 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":"

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":"

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":"

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":"

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":"

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.

"},{"location":"fep/5711/#references","title":"References","text":""},{"location":"fep/5711/#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/5bf0/","title":"FEP-5bf0: Collection sorting and filtering","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Michael Puckett michael@puckett.contact WITHDRAWN 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.

"},{"location":"fep/5bf0/#references","title":"References","text":""},{"location":"fep/5bf0/#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/5e53/","title":"FEP-5e53: Opt-out Preference Signals","text":"Authors Status Date received Tracking issue Discussions Repository Don Marti dmarti@zgp.org DRAFT 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.

"},{"location":"fep/5e53/#deployment-considerations","title":"Deployment considerations","text":"

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":""},{"location":"fep/5e53/#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/5feb/","title":"FEP-5feb: Search indexing consent for actors","text":"Authors Status Date received Tracking issue Discussions Repository Claire claire.fep-1d7d@sitedethib.com DRAFT 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.

"},{"location":"fep/5feb/#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/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.

"},{"location":"fep/5feb/#example","title":"Example","text":"
{\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).

"},{"location":"fep/5feb/#handling-updates-to-the-indexable-attribute","title":"Handling updates to the 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.

"},{"location":"fep/5feb/#security-considerations","title":"Security considerations","text":"

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":""},{"location":"fep/5feb/#references","title":"References","text":""},{"location":"fep/5feb/#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/612d/","title":"FEP-612d: Identifying ActivityPub Objects through DNS","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Helge helge.krueger@gmail.com WITHDRAWN 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.

"},{"location":"fep/612d/#discussion","title":"Discussion","text":"

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.

"},{"location":"fep/612d/#html-link-header","title":"HTML Link header","text":"

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":""},{"location":"fep/612d/#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/61cf/","title":"FEP-61cf: The OpenWebAuth Protocol","text":"Authors Status Type Date received Tracking issue Discussions Repository FenTiger @FenTiger@zotum.net DRAFT 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:

"},{"location":"fep/61cf/#starting-the-login-flow","title":"Starting the login flow","text":"

The OpenWebAuth login flow can begin in one of two ways:

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.

"},{"location":"fep/61cf/#operation-of-the-protocol","title":"Operation of the protocol","text":"

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.

"},{"location":"fep/61cf/#3-target-instance-provides-a-token","title":"3. Target instance provides a token","text":"

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.

"},{"location":"fep/61cf/#4-home-instance-receives-the-token","title":"4. Home instance receives the token","text":"

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.

"},{"location":"fep/61cf/#5-user-returns-to-target-instance","title":"5. User returns to target instance","text":"

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.

"},{"location":"fep/61cf/#target-instances-login-check","title":"Target instance's login check","text":"

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.

"},{"location":"fep/61cf/#target-instances-token-endpoint","title":"Target instance's token endpoint","text":"

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":""},{"location":"fep/61cf/#security-considerations","title":"Security Considerations","text":""},{"location":"fep/61cf/#information-leakage","title":"Information leakage","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.

"},{"location":"fep/61cf/#impersonation-attack","title":"Impersonation attack","text":"

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.

"},{"location":"fep/61cf/#open-redirection","title":"Open redirection","text":"

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.

"},{"location":"fep/61cf/#references","title":"References","text":""},{"location":"fep/61cf/#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/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.uk WITHDRAWN 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.

"},{"location":"fep/6481/#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/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":""},{"location":"fep/6481/#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/6606/","title":"FEP-6606: ActivityPub client to server collections addressing conventions","text":"Authors Status Date received Tracking issue Discussions Repository Marius Orcsik marius@federated.id DRAFT 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":""},{"location":"fep/6606/#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/67ff/","title":"FEP-67ff: FEDERATION.md","text":"Authors Status Date received Date final Tracking issue Discussions Repository silverpill @silverpill@mitra.social FINAL 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.

"},{"location":"fep/67ff/#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/67ff/#structure","title":"Structure","text":"

The FEDERATION.md file can have arbitrary structure and content. The only requirements are:

"},{"location":"fep/67ff/#template","title":"Template","text":"

(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":""},{"location":"fep/67ff/#references","title":"References","text":""},{"location":"fep/67ff/#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/6fcd/","title":"FEP-6fcd: Account Export Container Format","text":"Authors Status Date received Tracking issue Discussions Repository Dmitri Zagidulin @dmitri@social.coop DRAFT 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:

"},{"location":"fep/6fcd/#inspirations-and-prior-art","title":"Inspirations and Prior Art","text":""},{"location":"fep/6fcd/#overall-concept","title":"Overall Concept","text":"
  1. Serialize export data into files and directories
  2. Add a lightweight manifest.yml file that describes what's in the files and directories
  3. Wrap everything into a .tar file.
"},{"location":"fep/6fcd/#manifest-file","title":"Manifest File","text":""},{"location":"fep/6fcd/#reserved-properties","title":"Reserved Properties","text":""},{"location":"fep/6fcd/#activitypub-export-example","title":"ActivityPub Export Example","text":"

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":""},{"location":"fep/6fcd/#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/73cd/","title":"FEP-73cd: Migration User Stories","text":"Authors Status Date received Tracking issue Discussions Repository Bumblefudge <@learningproof.xyz> / @by_caballero@mastodon.social DRAFT 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.

  1. Alice wants to move her account from Alpha to Gamma, both of which are online and federated to one another, without losing her follow relationships in either direction. Four possible variants, not mutually exclusive:
  2. Bob is asked to leave Alpha by its moderation team, who have disabled new posts on that account but are allowing Bob to execute a one-way, permanent migration to a new server of Bob's choosing as a courtesy. Bob logs in to Alpha and permanently migrates to a new server with which Alpha is federated, allowing a server-to-server connection. Bob instead finds a new home on the server Beta, which is specifically de-federated by Alpha for incompatible moderation policies. Bob would like to announce to his followers his new account, without Alpha and Beta having to communicate with one another (since Alpha refuses connections with Beta altogether).
  3. Charlie would like to move his profile, followers, and his years of original content to Gamma from Delta, because the latter was recently and unexpectedly taken offline by government intervention. Before going dark, Gamma had already authorized a custom client for Charlie, which he used to sign each posts with a self-managed private key, and Charlie had backed up his followers/following lists. Delta is able to load all of the above and authenticate them to display on his new account.
  4. Bob would also like to move his previous content from Alpha to Beta, which are defederated. After having his service partially limited, he is able to export his archive of Alpha content (including media uploads but no moderation records, as Alpha does not generate any) and import it to Beta to be considered for republication.
  5. Charlie also managed to expert an archive of his Gamma content after authorizing Charlie's custom client (including media uploads and moderation records), and is able to use said client to authenticate this content to Delta, who can consider it for republication despite Gamma being offline.
  6. After using Delta for some time, Charlie moves on to server Epsilon, which Daniel wants to migrate to from Delta. Epsilon has no moderation policy because Daniel is its only user and he has full admin rights over it. Epsilon loads a recent backup, skipping over the moderation policies exported by Delta.
  7. At time of import, Delta does not support all the same features and Activity types that Gamma did at time of export. Two variants, not mutually exclusive:
"},{"location":"fep/73cd/#use-case-to-fep-mapping","title":"Use-Case to FEP Mapping","text":"

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>)...

Use cases: 1A 1B 1C 1D 2 3 4 5 6 7A 7B FEP-7628 \"Move Actor\" (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":""},{"location":"fep/73cd/#references","title":"References","text":""},{"location":"fep/73cd/#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/7458/","title":"FEP-7458: Using the replies collection","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 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.

"},{"location":"fep/7458/#problem-statement","title":"Problem statement","text":"

(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.

"},{"location":"fep/7458/#example-flow","title":"Example flow","text":"

(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.

"},{"location":"fep/7458/#behavioral-requirements","title":"Behavioral requirements","text":""},{"location":"fep/7458/#references","title":"References","text":""},{"location":"fep/7458/#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/7502/","title":"FEP-7502: Limiting visibility to authenticated actors","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 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.

"},{"location":"fep/7502/#prior-art","title":"Prior art","text":"

(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.

"},{"location":"fep/7502/#proposal","title":"Proposal","text":"

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":""},{"location":"fep/7502/#implementations","title":"Implementations","text":"

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.

"},{"location":"fep/7502/#terms-defined","title":"Terms defined","text":"Authenticated URI 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":""},{"location":"fep/7502/#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/7628/","title":"FEP-7628: Move actor","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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.

"},{"location":"fep/7628/#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/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:

"},{"location":"fep/7628/#migration","title":"Migration","text":"

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:

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:

  1. Move sent by the old actor (push mode). In this case, the old server needs to be online.
  2. 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.

movedTo

The location of the new primary actor. The actor containing this property should be considered inactive.

copiedTo

The location of the new primary actor.

"},{"location":"fep/7628/#references","title":"References","text":""},{"location":"fep/7628/#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/76ea/","title":"FEP-76ea: Conversation Threads","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.org DRAFT 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.

  1. As an ActivityPub developer, I want to be able to determine if two objects are part of the same conversation thread, so that I can display them together in a conversation view. This is possible to do by walking up the 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.
  2. As an ActivityPub developer, I want to get the full conversation thread of an object efficiently, so I can show the object in context. Again, using the inReplyTo chain and replies collections can be slow and inefficient, especially if the thread is deep or has many objects.
  3. As an ActivityPub developer, I want to get the most-recently-added objects in a conversation thread, so I can keep my view of the thread up-to-date. This is hard to do with inReplyTo and replies, and possibly requires fetching every single replies collection in the tree.
  4. As an ActivityPub developer, I want to be able to identify the original post of a conversation thread, so I can show the thread in context. This content object is usually treated differently by clients, so it's useful to be able to identify it.
  5. As a commenter, I want my reply to a content object to be part of the same thread, so I am participating in a larger context.
  6. As the original poster, I want to be able to curate the conversation thread, so I can remove spam, off-topic, or abusive content from the thread. Maintaining a curated thread is an important service that the original poster provides. Curating the replies collection is not sufficient, because it only contains direct replies to the object, not the full conversation tree.
  7. As a user, I want to comment on or about a content object without being part of the conversation thread, so I can discuss it in my own social sphere. Not all continued conversation should be part of the original reply tree.
  8. As a user, I want to branch a reply into its own conversation thread, so I can have a separate conversation about a sub-topic. This is common in forums and other threaded discussion software.
  9. As a user, I want to graft part of a conversation thread into another thread, so I can consolidate related conversations. This is common in forums and other threaded discussion software.
"},{"location":"fep/76ea/#context","title":"Context","text":"

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.

"},{"location":"fep/76ea/#root","title":"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.

"},{"location":"fep/76ea/#behavioral-specification","title":"Behavioral specification","text":"

This covers recommended behavior for processors that implement the thread property.

"},{"location":"fep/76ea/#original-posts","title":"Original posts","text":"

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.

"},{"location":"fep/76ea/#replies-in-the-thread","title":"Replies in the thread","text":"

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.

"},{"location":"fep/76ea/#branching","title":"Branching","text":"

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.

"},{"location":"fep/76ea/#grafting","title":"Grafting","text":"

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.

"},{"location":"fep/76ea/#thread-maintenance","title":"Thread maintenance","text":"

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.

"},{"location":"fep/76ea/#examples","title":"Examples","text":""},{"location":"fep/76ea/#example-1","title":"Example 1","text":"

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.

"},{"location":"fep/76ea/#example-3","title":"Example 3","text":"

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.

"},{"location":"fep/76ea/#previous-work","title":"Previous work","text":"

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.

"},{"location":"fep/76ea/#references","title":"References","text":""},{"location":"fep/76ea/#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/7888/","title":"FEP-7888: Demystifying the context property","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 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.

"},{"location":"fep/7888/#overview","title":"Overview","text":"

(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:

The requirements below can be summarized like so:

"},{"location":"fep/7888/#publishing-context","title":"Publishing context","text":"

When generating an object with a context as a publisher:

"},{"location":"fep/7888/#purpose","title":"Purpose","text":"

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.

"},{"location":"fep/7888/#dereferencing-and-resolving","title":"Dereferencing and resolving","text":"

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:

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.)

"},{"location":"fep/7888/#consuming-context","title":"Consuming context","text":"

When encountering an object with a context as a consumer or browser:

"},{"location":"fep/7888/#group-objects-by-context","title":"Group objects by 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).

"},{"location":"fep/7888/#canonical-collections-of-objects-within-an-authoritative-context","title":"Canonical collections of objects within an authoritative context","text":"

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:

"},{"location":"fep/7888/#interacting-with-context","title":"Interacting with 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:

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.

"},{"location":"fep/7888/#keeping-relevant-entities-in-the-loop","title":"Keeping relevant entities in the loop","text":"

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.

"},{"location":"fep/7888/#appendix-a-rationale","title":"Appendix A: Rationale","text":"

(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:

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:

"},{"location":"fep/7888/#appendix-b-examples","title":"Appendix B: Examples","text":"

(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.

"},{"location":"fep/7888/#example-3-encountering-multiple-contexts","title":"Example 3: Encountering multiple contexts","text":"

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.

"},{"location":"fep/7888/#appendix-c-creating-and-maintaining-contexts-and-their-associated-collections-using-activitypub-c2s","title":"Appendix C: Creating and maintaining contexts and their associated collections using ActivityPub C2S","text":"

(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:

  1. Create the canonical Collection that will be associated with the context. Save the generated Collection id to be used in the next step.
  2. Create the Object that will be used as 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.
  3. Create the Object that will exist within the context, and specify the 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.
  4. Add the Object to the context's canonical Collection, using the 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.
"},{"location":"fep/7888/#references","title":"References","text":""},{"location":"fep/7888/#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/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.xyz DRAFT 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:

  1. Identity hosting (hosting of the Actor profile object, which serves as the source of truth for all others in the list)
  2. Authentication and authorship/provenance of objects
  3. Key management (used for HTTP Signatures for Server to Server request authentication)
  4. ActivityPub Object and Collection storage (typically in a database)
  5. Message Transfer Services (hosting of inboxes and outboxes, delivering of outgoing objects, notifications, etc)
  6. Instance-based content moderation
  7. Community discovery (via the instance's Local feed)
  8. Front-end clients, i.e. \u201cwebsite\u201d and/or mobile app (most current implementations bundle the backend system with a made-for-purpose front end that runs off the same server)

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":"
  1. Sign everything: Recognize client-side cryptographic signatures as proof of authorship, in addition to the current practice of relying solely on the instance URL:
  2. add verification key(s) to the Actor object via FEP-521a
  3. Sign individual activities using the same key(s), as specified in FEP-8b32: Object Integrity Proofs
  4. B.Y.O. Actor ID: Using Object Integrity proofs enables Identity Hosting to be separated from the other instance concerns. Actor profiles can now be hosted separately from the instance, which in turn enables service providers to offer their users a \u201cBYO (Bring Your Own) domain name\u201d feature.
  5. Separate Inbox/Outbox: (Optional) The previous steps enable message transfer and Inbox/Outbox hosting to be outsourced to separate service providers (the Actor profile links to these in the usual manner).
  6. Separate Object and Collection hosting: (Optional) Similarly, AP Objects and Collections can now be stored on domains separate from the Actor's domain (since authorship and controller-ship can be proven cryptographically, in a domain-independent way). This enables the user to migrate storage service providers without having to change their Actor ID.
"},{"location":"fep/7952/#2-implement-portable-migration-stable-object-ids","title":"2. Implement Portable (migration-stable) Object IDs","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:

As 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:

"},{"location":"fep/7952/#3-create-account-exportimport-features-that-assume-actor-relative-urls-and-segmented-services","title":"3. Create account export/import features that assume Actor-Relative URLs and segmented services","text":"

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:

"},{"location":"fep/7952/#references","title":"References","text":""},{"location":"fep/7952/#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/7aa9/","title":"FEP-7aa9: Featuring recommendations using a dedicated collection","text":"Authors Status Date received Tracking issue Discussions Repository David Roetzel david@joinmastodon.org DRAFT 2026-06-12 #862 Discussions codeberg"},{"location":"fep/7aa9/#summary","title":"Summary","text":"

This FEP describes both a new object type and mechanism to allow users to curate collections of other users (actors) and possibly other objects that they would like to recommend to others. These collections, sometimes referred to as \"Starter Packs\", help new users find interesting people and content to follow.

Users are able to opt in to being included in these collections and can remove themselves from them. Problematic collections can be reported and moderated just like other content.

"},{"location":"fep/7aa9/#background","title":"Background","text":"

\"Starter Packs\", a feature pioneered by Bluesky, have proven to be a brilliant way to help new users to find exactly the right people to follow. This can be an important tool to combat the \"empty feed\" problem that many new users experience on the fediverse and that sometimes turns them away. As such, there has been a lot of interest in implementing Starter Packs on the fediverse.

But while the basic idea of a \"Starter Pack\" is quite simple, the federated nature of the network poses some challenges that need to be overcome. For example it should be possible to interact with remote \"Starter Packs\" that were created on a different server and possibly by a different software.

Last but not least, \"Starter Packs\" need to be handled with care as they can be an easy vector for harassment. Users need control over which \"Starter Packs\" they are included in and \"Starter Packs\" need to be subject to the same moderation procedures already in place for other types of content.

GoToSocial has pioneered \"Interaction Policies\" to model a user's preferences for different kinds of interactions. And in FEP-044f Mastodon has expanded on this idea with the addition of verifiable \"stamps\" to prove user's consent.

This FEP takes those concepts and applies them to a user-curated and federated collection of actors (or any kind of object really) called FeaturedCollection. The name was chosen because some platforms already announced they do not plan to use the term \"Starter Pack\" and to illustrate that other uses, i.e. featuring other objects than just actors, are possible.

"},{"location":"fep/7aa9/#representation-of-featured-collections","title":"Representation of Featured Collections","text":"

Featured collections are represented by a new object type, FeaturedCollection (https://w3id.org/fep/7aa9#FeaturedCollection). FeaturedCollection is a subtype of OrderedCollection and inherits all of its properties.

A FeaturedCollection MUST have the following properties:

In addition, a FeaturedCollection MAY have the following properties:

It is worth emphasizing that both icon and image are separately optional. Providers of FeaturedCollections may choose to supply both, only one, or neither. Applications displaying FeaturedCollections may also elect to show or omit either or both images, depending on what makes sense in their UI design and the specific situation. This FEP considers these images decorative in nature, meaning they should not be the only source of important information.

This FEP also introduces a new property that MAY optionally be used in a FeaturedCollection:

The individual items in the FeaturedCollection are of the type FeaturedItem (https://w3id.org/fep/7aa9#FeaturedItem) which is a subtype of Object. A FeaturedItem MUST have the following property:

In the case that the featured object is an actor it MUST also include the following property:

In addition, a FeaturedItem MAY have the following property:

Please note that initially the featured objects are expected to be actors. But the specification is intentionally open to also include other object types. The most obvious one that platforms might want to add in the future is Hashtag.

Example featured collection:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/7aa9\",\n    {\n      \"Hashtag\": \"as:Hashtag\",\n      \"sensitive\": \"as:sensitive\",\n      \"discoverable\": \"https://joinmastodon.org/ns#discoverable\"\n    }\n  ],\n  \"type\": \"FeaturedCollection\",\n  \"id\": \"https://fedi.example.com/users/alice/featured/23\",\n  \"name\": \"Cute cats\",\n  \"summary\": \"A selection of accounts that I follow because of their interesting cat content.\",\n  \"attributedTo\": \"https://fedi.example.com/users/alice\",\n  \"sensitive\": false,\n  \"discoverable\": true,\n  \"topic\": {\n    \"type\": \"Hashtag\",\n    \"name\": \"#cats\"\n  },\n  \"icon\": {\n    \"type\": \"Image\",\n    \"url\": {\n      \"type\": \"Link\",\n      \"mediaType\": \"image/jpeg\",\n      \"href\": \"https://fedi.example.com/assets/alice_sp_23.jpg\"\n    }\n  },\n  \"totalItems\": 2,\n  \"orderedItems\": [\n    {\n      \"id\": \"https://fedi.example.com/users/alice/featured/23/items/1\",\n      \"type\": \"FeaturedItem\",\n      \"featuredObject\": \"https://fedi.example.com/users/jennifer\",\n      \"featureAuthorization\": \"https://fedi.example.com/users/jennifer/stamps/12\",\n      \"published\": \"2025-08-14T12:13:22Z\"\n    },\n    {\n      \"id\": \"https://fedi.example.com/users/alice/featured/23/items/2\",\n      \"type\": \"FeaturedItem\",\n      \"featuredObject\": \"https://other.example.com/users/jim\",\n      \"featureAuthorization\": \"https://other.example.com/users/jim/stamps/21\",\n      \"published\": \"2025-08-14T12:14:51Z\"\n    }\n  ],\n  \"published\": \"2025-08-14T12:12:12Z\",\n  \"updated\": \"2025-08-14T13:17:25\"\n}\n

Very large lists of items do not make much sense from a UX perspective. E.g. not many users will want to blindly follow a couple of hundred of unknown accounts. And forcing remote servers to potentially fetch a lot of unknown actors is a vector for Denial of Service (DOS). That is why fediverse software SHOULD both impose a limit to the number of items that can be added to a featured collection and that they will handle when dealing with remote collections. The proposed maximum of items is 150 but implementations MAY have different limits.

All properties mentioned are expected to have at most one value unless stated otherwise.

"},{"location":"fep/7aa9/#featured-collections-on-actors","title":"Featured Collections on Actors","text":"

Featured collections are created by individual actors. As such they SHOULD become part of a new collection property on the actor, featuredCollections. This way other servers on the fediverse can easily discover them.

"},{"location":"fep/7aa9/#federating-changes-and-opportunistic-updating","title":"Federating Changes and Opportunistic Updating","text":"

When a user creates a new FeaturedCollection, this is then added to their actor's featuredCollections collection, an operation that can be federated as an Add activity.

Similarly, when a FeaturedItem is added to a FeaturedCollection, this can also be distributed in the form of an Add activity.

Remove activities can be sent in case of removal from one of the mentioned collections.

A FeaturedCollection can be discovered by different means, which means a server can never know exactly who knows about it. When addressing the aforementioned activities a server SHOULD thus use a heuristic to determine who to send them to. A reasonable heuristic could be to send all changes to an actor's featuredCollections collection to the actor's followers. Additionally all changes to an individual FeaturedCollection could also go to all actors in that collection (including the one that was just removed in case of Remove).

No such heuristic will ever be perfect though. Servers who know about a FeaturedCollection might still not receive relevant updates. This means that implementations SHOULD try to re-fetch these collections from time to time to make sure the content is still current.

"},{"location":"fep/7aa9/#interaction-policies-for-curated-collections","title":"Interaction Policies for Curated Collections","text":"

Users MUST be able to consent to being included in featured collections. To signal a user's preferences in that regard an interactionPolicy object, as first introduced by GoToSocial, MUST be added to the user's actor. This interactionPolicy MUST have a property canFeature.

Abbreviated example actor:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://gotosocial.org/ns\",\n    \"https://w3id.org/fep/7aa9\"\n  ],\n  \"id\": \"https://example.com/users/alice\",\n  \"type\": \"Person\",\n  \"interactionPolicy\": {\n    \"canFeature\": {\n      \"automaticApproval\": [ \"https://fedi.example.com/users/alice/followers\" ],\n      \"manualApproval\": [ \"https://www.w3.org/ns/activitystreams#Public\" ]\n    }\n  }\n  // ...\n}\n

The two properties of the canFeature object can be used to signal who is always allowed to feature this actor in a featured collection (automaticApproval) and who might do so but would need a manual approval (manualApproval).

The value in both cases MUST be an array consisting of actor objects, Collection of actor objects or the special collection https://www.w3.org/ns/activitystreams#Public. Note that the latter might also be represented as as:Public or simply Public. Implementations SHOULD handle all three possible representations.

In practice, the only values that SHOULD be used are the id of the actor itself (see below), the followers and the following collection of the actor and https://www.w3.org/ns/activitystreams#Public.

Actors not specifically mentioned or included in one of the collections are never allowed to feature the actor.

The absence of an interactionPolicy MUST be treated as missing consent and the affected actors MUST NOT be added to featured collections ever.

To make a policy of never wanting to be featured explicit, interactionPolicy.canFeature.automaticApproval SHOULD contain the actor's id as its single value. This is because an empty array is equivalent to a missing property under JSON-LD canonicalization.

In any case this general policy is just that, a general policy, and MUST NOT be confused with actual consent. This means that one can use this policy to determine which actors may be added to featured collections, but one always has to check if the approval for a specific featured collection was really given. See the next section for details.

Note that this is modeled closely after interaction policies for quote posts (see FEP-044f).

"},{"location":"fep/7aa9/#obtaining-consent","title":"Obtaining consent","text":"

While interaction policies signal an actor's general preferences any attempt to include an actor in a featured collection MUST ask for consent explicitly.

To do so a new activity type FeatureRequest is introduced. It has two mandatory properties:

When adding an actor to a featured collection the owner of said collection MUST send a FeatureRequest activity to the actor that is about to be added.

Example FeatureRequest:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/7aa9\"\n  ],\n  \"id\": \"https://fedi.example.com/users/alice/featured/23/requests/2\",\n  \"type\": \"FeatureRequest\",\n  \"object\": \"https://other.example.com/users/bob\",\n  \"instrument\": \"https://fedi.example.com/users/alice/featured/23\"\n}\n

In response the actor can either issue an Accept or a Reject activity. In case of automatic approval, those can be issued immediately. In case of manual approval, a user needs to be notified and asked before the answer can be sent.

In both cases, Accept or Reject, the object of the activity is the FeatureRequest. In case of an Accept the activity MUST also include a result property pointing to a FeatureAuthorization.

Example Accept activity:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\"\n  ],\n  \"type\": \"Accept\",\n  \"to\": \"https://fedi.example.com/users/alice\",\n  \"actor\": \"https://other.example.com/users/bob\",\n  \"object\": \"https://fedi.example.com/users/alice/featured/23/requests/2\",\n  \"result\": \"https://other.example.com/users/bob/stamps/1024\"\n}\n

Example Reject activity:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\"\n  ],\n  \"type\": \"Reject\",\n  \"to\": \"https://fedi.example.com/users/alice\",\n  \"actor\": \"https://other.example.com/users/bob\",\n  \"object\": \"https://fedi.example.com/users/alice/featured/23/requests/2\",\n}\n

When the server that issued the FeatureRequest receives an Accept it SHOULD add a new FeaturedItem to the FeaturedCollection in which case the featureAuthorization property MUST include the result of the Accept activity.

Example FeaturedItem resulting from the Accept above:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/7aa9\"\n  ],\n  \"id\": \"https://fedi.example.com/users/alice/featured/23/items/2\",\n  \"type\": \"FeaturedItem\",\n  \"object\": \"https://other.example.com/users/bob\",\n  \"featureAuthorization\": \"https://other.example.com/users/bob/stamps/1024\",\n  \"published\": \"2025-08-14T12:13:22Z\"\n}\n

In case of a Reject, a new FeaturedItem MUST NOT be created and nothing is added to the FeaturedCollection.

"},{"location":"fep/7aa9/#verification","title":"Verification","text":"

The FeatureAuthorization obtained through the Accept activity as described in the previous section serves as an \"approval stamp\", an object that can be used to verify that approval to be included in a featured collection was given.

A FeatureAuthorization MUST include the following properties:

Example FeatureAuthorization:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://gotosocial.org/ns\",\n    \"https://w3id.org/fep/7aa9\"\n  ],\n  \"id\": \"https://other.example.com/users/bob/stamps/1024\",\n  \"type\": \"FeatureAuthorization\",\n  \"interactingObject\": \"https://fedi.example.com/users/alice/featured/23\",\n  \"interactionTarget\": \"https://other.example.com/users/bob\"\n}\n

When processing a FeaturedCollection from a remote server the FeatureAuthorization of every FeaturedItem MUST be validated. If it is missing, cannot be resolved or the hosting service does not match the actor's the item MUST be ignored or removed from the collection before it is being displayed to users.

"},{"location":"fep/7aa9/#revocation","title":"Revocation","text":"

Actor's can opt out of being featured after the fact. In that case they MUST issue an Delete activity with the FeatureAuthorization as object.

Example Delete activity:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\"\n  ],\n  \"type\": \"Delete\",\n  \"to\": \"https://fedi.example.com/users/alice\",\n  \"actor\": \"https://other.example.com/users/bob\",\n  \"object\": {\n    \"id\": \"https://other.example.com/users/bob/stamps/1024\",\n    \"type\": \"FeatureAuthorization\",\n    \"interactingObject\": \"https://fedi.example.com/users/alice/featured/23\",\n    \"interactionTarget\": \"https://other.example.com/users/bob\"\n  }\n}\n

When a Delete activity for a FeatureAuthorization is received the affected FeaturedItem MUST be removed from the FeaturedCollection.

"},{"location":"fep/7aa9/#moderation","title":"Moderation","text":"

Featured collections can include language or imagery that are against a given server's rules. As such it MUST be possible to report them and to handle reports received.

Just like with other objects, a report is federated as a Flag activity and FeaturedCollection MAY be added to the list of reported objects:

Example activity:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\"\n  ],\n  \"id\": \"https://other.example.com/reports/17324\",\n  \"type\": \"Flag\",\n  \"actor\": \"https://other.example.com/actor\",\n  \"to\": \"https://fedi.example.com/users/alice\",\n  \"content\": \"Inappropriate language in collection description\",\n  \"objects\": [\n    \"https://fedi.example.com/users/alice\",\n    \"https://fedi.example.com/users/alice/featured/23\"\n  ]\n}\n
"},{"location":"fep/7aa9/#implementation-guidelines","title":"Implementation Guidelines","text":"

The mechanisms described offer a lot of flexibility and thus can lead to some complexity in implementations. But implementations do not have to be complex to work. There is a spectrum of possibilities.

At the lower end of that spectrum sit implementations that do not want to offer their users any control or agency at all. If you think it should always be possible for users to be featured, you can add the same interaction policy to every actor and simply always return positive authorizations. This can be a fully automated process with little overhead.

Similarly an implementation could decide to never allow their users to be added. So either no, or a special interaction policy can be included and authorizations always denied.

At the other end of the spectrum lie implementations that offer their users full flexibility. This would include fine-grained settings for both manual and automatic approval, leading to complex interaction policies. And for manual approval they would probably need a special UI notifying users of a request to be featured with affordances to either accept or deny that request.

Of course there is a lot of middle ground here. A reasonable approach that sits somewhat in the middle could use automatic approval only and offer users a single setting for their preference with a handful of options to chose from. To make up for the lack of manual approval, removing oneself from a featured collection could be made very easy.

"},{"location":"fep/7aa9/#implementations","title":"Implementations","text":""},{"location":"fep/7aa9/#references","title":"References","text":""},{"location":"fep/7aa9/#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/7b29/","title":"FEP-7b29: Federated Signup Requests","text":"Authors Status Date received Tracking issue Discussions Repository Ben Pate \\<@benpate@mastodon.social>, Dan Supernault \\<@dansup@mastodon.social> DRAFT 2026-03-26 #822 Discussions codeberg"},{"location":"fep/7b29/#summary","title":"Summary","text":"

People don't sign up for a new social website in a vacuum. Instead, this is one step in a larger goal that they are trying to accomplish -- such as following a new account, or liking or replying to a particular post.

But this process is often disjointed and filled with \"dead ends\" in the user experience.

\"Federated Signups\" give websites the tools to create a smooth signup process even when the user is bounced between several websites. It accomplishes this by including standardized query parameters in signup referral links to the user's new Fediverse server. This prevents \"dead ends\" and allows the user's new server to complete the original task that started their signup.

"},{"location":"fep/7b29/#1-requirements","title":"1. Requirements","text":"

The key words \"MUST\", \"SHOULD\", and \"MAY\" are to be interpreted as described in RFC2119.

For the purposes of this document, a \"remote server\" is any website on the Internet where this workflow begins, such as a news or informational website. A \"website visitor\" is a person who is consuming the content on a remote server.

Remote servers refer their website visitors to \"potential home servers\", where they may choose to sign up. When a person creates an account on a potential home server, they become a \"user\" and that potential home server simply becomes their \"home server.\"

"},{"location":"fep/7b29/#2-the-problem","title":"2. The Problem","text":"

Currently, most remote servers methods for their website visitors to stay connected with them after their initial visit. For example, visitors who read an article on Substack and Patreon have the opportunity to \"Follow\" the author of the article and be notified when new articles are published.

This workflow is difficult to achieve for new users of a distributed environment.

For instance, a Fediverse news website (such as WeDistribute.org) hosts its own ActivityPub actors, but does not allow website visitors to create accounts on their website. Instead, publishers like this depend on their visitors following from their own \"home server\" - a Fediverse account on an entirely separate domain.

If the visitor already has a home server, then FEP-3b86 Activity Intents provides a smooth workflow for existing Fediverse users to follow.

But this process breaks down if the website visitor does not already have a Fediverse account. At best, publishers can recommend a Fediverse server for visitors to join, but even still, the visitor's original goal is lost. There is no way for them to connect the new account signup back to their original goal, in order to continue following, liking, or replying to the web page on the remote server.

And from the remote server's point of view, that website visitor goes into a black hole. It never finds out if that visitor actually joined and completed their task or not. If the website visitor was half-way into a task -- such as purchasing access to private content -- they may have to start over once they have created an account on their new home server.

If websites are to be gateways for new people to find and join the Fediverse, then we must unify this workflow into a single seamless process, both before and after new users join.

"},{"location":"fep/7b29/#3-federated-signups","title":"3. Federated Signups","text":"

Federated signups define a common way for remote servers to pass metadata to a potential home server when referring website visitors to sign up.

This state information takes the form of optional query parameters passed to the home server's signup page, which allows the home server to complete the user's original goal (for instance, following an author or replying to a post)

This document defines four standard query parameters:

Values for each of these parameters MUST be URL encoded values consistent with URL query parameter standards.

These parameters are merely workflow requests from the remote server to the potential home server. Workflows defined on remote servers MUST NOT depend on receiving a response from potential home servers.

"},{"location":"fep/7b29/#31-pleasefollow","title":"3.1. please.follow","text":"

If a website visitor wishes to Follow a particular actor on a remote website, but does not yet have a Fediverse identity, then the remote website SHOULD refer them to one or more Fediverse servers where they can establish an account. The link provided by the remote server MAY include a please.follow query parameter.

This parameter MUST be a comma separated list of one or more URLS. The URLs MUST refer to one of these two values:

Once the user has established a new account, the home server SHOULD inspect the list of actors and collections, then provide the new user with the option choose which of them to follow.

Home servers MAY apply additional rules to this list, for instance adding additional recommended accounts to follow, or removing values from the provided list for other policy reasons.

Example referral URL using propose.follow:

https://homeserver.social/signup?please.follow=https://remoteserver.social/@actor-to-follow,https://remoteserver.social/starter-pack-to-follow\n
"},{"location":"fep/7b29/#32-pleaseview","title":"3.2. please.view","text":"

If a website visitor wishes to interact with a particular piece of content on a remote website (i.e. to like, boost, or reply to a web page) but does not yet have a Fediverse identity, then the remote website SHOULD refer them to one or more Fediverse servers where they can establish an account. The link provided by the remote server MAY include a please.view query parameter.

This parameter MUST be a single URL that links to the original content.

Once the user has established a new account, the home server SHOULD look up the ActivityStreams representation of the URL and present it to the user.

Home servers MAY apply additional rules to requested URLs. For instance, if a provided URL belongs to a blocked or banned website or user, then the new home server MAY refuse to display it to the new user.

Example referral URL using please.view:

https://homeserver.social/signup?please.view=https://remoteserver.social/my-latest-article\n
"},{"location":"fep/7b29/#321-pleasehighlight","title":"3.2.1 please.highlight","text":"

As an additional option, when a remote server sends a please.view parameter, it MAY also send a please.highlight parameter. This identifies a single activity (such as Like, Announce, Arrive, etc) for the new home server to feature on the page displayed to new users.

Home servers MAY or MAY NOT use this parameter. It is provided as guidance to the new home server to help the new user complete their original task.

Consider the example of a website visitor who clicks a \"Like\" button on the remote server, and is then forwarded to a new home server and completes the signup process there. When their new home server displays the original resource where they clicked \"Like\", it MAY also highlight the \"Like\" button in some way on its corresponding page. This MAY be by color, size, positioning, or any other method determined by the home server.

"},{"location":"fep/7b29/#33-pleasenotify","title":"3.3. please.notify","text":"

When a remote website refers a visitor to a new home server, it may have incomplete tasks in process that would benefit by knowing that the visitor has successfully created a new account. For example, if the visitor has purchased access to private content, then the remote website may want to link those privileges to the newly-created identity.

The please.notify parameter asks the new home server to send an out-of-band HTTP POST to a URL specified by the remote server.

The URL specified in the please.notify parameter MUST bePercent Encoded.

The URL specified in the please.notify parameter MAY include a single template string value: {id}.

Home servers MAY take actions based on this parameter, or MAY simply disregard it.

To support this parameter, a home server MUST replace this {id} value with the actor id of the newly created actor. This is the requested notification URL. Then, the home server MUST send an HTTP POST request with no body to the notification URL.

"},{"location":"fep/7b29/#40-same-server-signups","title":"4.0. Same Server Signups","text":"

Federated Signups help improve the signup workflow even if the remote server and home server are the same. If a Fediverse-enabled website allows anonymous user signups, it can still provide a smoother user experience by passing the four standard parameters to its own signup pages.

"},{"location":"fep/7b29/#50-instance-choosers","title":"5.0. Instance Choosers","text":"

There are a growing number of \"instance chooser\" websites that help potential new users to navigate the wide number of Fediverse servers to choose from.

\"Instance chooser\" websites SHOULD accept the four standard parameters, and then forward these values on to the signup page for the instance that the user ultimately picks.

This allows remote servers to let their visitors pick a new instance according to the rules provided by the instance chooser, and still request these important signup actions from the selected home server.

"},{"location":"fep/7b29/#60-implementations","title":"6.0. Implementations","text":""},{"location":"fep/7b29/#61-home-servers","title":"6.1 Home Servers","text":"

This is a list of \"home servers\" that publish Activity Intent endpoints.

"},{"location":"fep/7b29/#62-clients","title":"6.2 Clients","text":"

This is a list of client tools that allow end-users to use Activity Intents on remote websites.

"},{"location":"fep/7b29/#references","title":"References","text":""},{"location":"fep/7b29/#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/7d8c/","title":"FEP-7d8c: Documentation: Automation of FEP","text":"Authors Status Date received Tracking issue Discussions Repository Helge @helge@mymath.rocks DRAFT 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:

Automation 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.

"},{"location":"fep/7d8c/#running-tests","title":"Running tests","text":"

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.

"},{"location":"fep/7d8c/#building-the-website","title":"Building the website","text":"

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:

"},{"location":"fep/7d8c/#automatic-deployment","title":"Automatic deployment","text":"

See website.yml.

"},{"location":"fep/7d8c/#changes","title":"Changes","text":""},{"location":"fep/7d8c/#references","title":"References","text":""},{"location":"fep/7d8c/#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/82f6/","title":"FEP-82f6: Actor statuses","text":"Authors Status Date received Tracking issue Discussions Repository Gregory Klyushnikov activitypub@grishka.me DRAFT 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:

"},{"location":"fep/82f6/#actor-fields","title":"Actor fields","text":"

The following two OPTIONAL fields are added to actors:

"},{"location":"fep/82f6/#activities","title":"Activities","text":""},{"location":"fep/82f6/#creating-a-status-update","title":"Creating a status update","text":"

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.

"},{"location":"fep/82f6/#clearing-the-status","title":"Clearing the status","text":"

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.

"},{"location":"fep/82f6/#deleting-the-status","title":"Deleting the status","text":"

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.

"},{"location":"fep/82f6/#other-activities-in-relation-to-statuses","title":"Other activities in relation to statuses","text":"

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.

"},{"location":"fep/82f6/#context","title":"Context","text":"

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":""},{"location":"fep/82f6/#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/844e/","title":"FEP-844e: Capability discovery","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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 objects, each containing the following properties:

The order of objects in the array is not important.

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":""},{"location":"fep/844e/#references","title":"References","text":""},{"location":"fep/844e/#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/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.com DRAFT 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.

"},{"location":"fep/888d/#acknowledgements","title":"Acknowledgements","text":"

(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.

"},{"location":"fep/888d/#requirements","title":"Requirements","text":"

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:

At minimum, the following redirects SHOULD resolve as follows:

Additionally, the following MAY resolve:

"},{"location":"fep/888d/#mapping-w3idorgfep-to-fediversefep-on-codeberg","title":"Mapping w3id.org/fep to fediverse/fep on Codeberg","text":"

At 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.

"},{"location":"fep/888d/#example","title":"Example","text":"

(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.

"},{"location":"fep/888d/#example-using-paths","title":"Example using paths","text":"

(This section is non-normative.)

For example, say we wanted to define the following terms within the current FEP-888d:

The 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:

"},{"location":"fep/888d/#defining-terms-in-a-machine-readable-way","title":"Defining terms in a machine-readable way","text":"

(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:

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:

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:

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:

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.

"},{"location":"fep/888d/#using-the-python-script","title":"Using the python script","text":"

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":""},{"location":"fep/888d/#algorithm","title":"Algorithm","text":""},{"location":"fep/888d/#usage","title":"Usage","text":""},{"location":"fep/888d/#example-terms-defined-by-this-fep","title":"Example terms defined by this FEP","text":"

(This section is non-normative.)

Using the same fictitious terms from the above examples:

We 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:

SomeType 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
\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":""},{"location":"fep/888d/#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/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.

"},{"location":"fep/8967/#link-preview","title":"Link preview","text":"

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:

Consumers 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>.

"},{"location":"fep/8967/#alternative-approaches","title":"Alternative approaches","text":"

(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.

"},{"location":"fep/8967/#implementations","title":"Implementations","text":""},{"location":"fep/8967/#references","title":"References","text":""},{"location":"fep/8967/#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/","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.org DRAFT 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.

"},{"location":"fep/8a8e/#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 context of attributes: Attributes that have no context prefix are to be interpreted as being in the https://www.w3.org/ns/activitystreams context.

"},{"location":"fep/8a8e/#object-type-event","title":"Object Type Event","text":"

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:

{\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.

"},{"location":"fep/8a8e/#time-zone","title":"Time zone","text":"

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:

"},{"location":"fep/8a8e/#physical","title":"Physical","text":"

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.

"},{"location":"fep/8a8e/#rsvp-attendee-management","title":"RSVP (Attendee Management)","text":""},{"location":"fep/8a8e/#not-handling-rsvp","title":"Not handling RSVP","text":"

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.

"},{"location":"fep/8a8e/#signaling-how-joins-are-handled","title":"Signaling how joins are handled","text":"

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.

"},{"location":"fep/8a8e/#showing-rsvp-status-via-activitypub","title":"Showing RSVP status via ActivityPub","text":"

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.

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.

"},{"location":"fep/8a8e/#event-banner-and-poster-images","title":"Event Banner and Poster Images","text":""},{"location":"fep/8a8e/#flyer","title":"Flyer","text":"

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.

"},{"location":"fep/8a8e/#banner","title":"Banner","text":"

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).

"},{"location":"fep/8a8e/#organizers-collection","title":"Organizers Collection","text":"

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.

"},{"location":"fep/8a8e/#fallback-to-attributedto","title":"Fallback to 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.

"},{"location":"fep/8a8e/#upcoming-events-collection-for-actors","title":"Upcoming Events Collection for Actors","text":"

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:

"},{"location":"fep/8a8e/#terms","title":"Terms","text":"

Note that the terms joinMode and externalParticipationUrl are compatible with and also leaned on the extensions defined by Mobilizon.

displayEndTime 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
\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. 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
\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\"} ] }

"},{"location":"fep/8a8e/EventCancelled/","title":"EventCancelled","text":"

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.\" } ] }

"},{"location":"fep/8a8e/attendees/","title":"attendees","text":"

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.\" } ] } }

"},{"location":"fep/8a8e/category/","title":"category","text":"

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 }

"},{"location":"fep/8a8e/eventStatus/","title":"eventStatus","text":"

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/\" }

"},{"location":"fep/8a8e/isBannerImage/","title":"isBannerImage","text":"

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 } ] }

"},{"location":"fep/8a8e/joinMode/","title":"joinMode","text":"

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.\" } ] } }

"},{"location":"fep/8a8e/previousStartTime/","title":"previousStartTime","text":"

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\" ] }

"},{"location":"fep/8a8e/timezone/","title":"timezone","text":"

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 }

"},{"location":"fep/8a8e/upcomingEvents/","title":"upcomingEvents","text":"

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\" } ] } }

"},{"location":"fep/8b32/","title":"FEP-8b32: Object Integrity Proofs","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social 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 offline verification 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.

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.

Objects identified using fragment identifiers SHOULD NOT contain integrity proofs.

Objects SHOULD NOT contain more than one integrity proof.

"},{"location":"fep/8b32/#proof-verification","title":"Proof verification","text":"

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 a verifier encounters an integrity proof that uses a verification method or cryptosuite that it doesn't support, it SHOULD ignore the proof and try other authentication methods.

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:

"},{"location":"fep/8b32/#backward-compatibility","title":"Backward compatibility","text":"

Integrity proofs and linked data signatures can be used together, as they rely on different properties (proof and signature, respectively).

If compatibility 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.

"},{"location":"fep/8b32/#security-considerations","title":"Security considerations","text":"

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.

Integrity proofs can be repudiated by rotating the verification method.

"},{"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":""},{"location":"fep/8b32/#implementations","title":"Implementations","text":""},{"location":"fep/8b32/#use-cases","title":"Use cases","text":""},{"location":"fep/8b32/#references","title":"References","text":""},{"location":"fep/8b32/#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/8c13/","title":"FEP-8c13: Context-Authority Routing with Object Integrity Proofs for Restricted Threads","text":"Authors Status Date received Tracking issue Discussions Repository Dmitry Skavish skavish@gmail.com DRAFT 2026-06-27 #870 Discussions codeberg"},{"location":"fep/8c13/#summary","title":"Summary","text":"

In ActivityPub today, a reply to a \"followers-only\" post is delivered only to the replier's followers. Everyone else in the conversation - including people who follow the original author - never sees it, so the thread fragments into inconsistent partial views.

This proposal fixes that by giving every restricted thread a single coordinating server: the Context Authority, which is the server that hosts the thread's root post. All replies, reactions, edits, and deletes for the thread are sent to that one server, which validates them and fans them out to everyone currently allowed to see the thread. Because all traffic flows through one authority, every participant converges on the same view.

"},{"location":"fep/8c13/#how-it-works-end-to-end","title":"How it works, end to end","text":"
  1. Every thread has a Context Object - a dereferenceable collection (per FEP-7888) that represents the thread and is hosted by the root author's server. Its URI is the context value carried on posts in the thread.
  2. A sender replies normally. They use ordinary to/cc addressing (e.g. the author and their followers) and include the thread's context. They do not put the context URI in to/cc. They deliver the reply to the root author's inbox.
  3. The Context Authority routes it. The root author's server recognizes itself as the Context Authority for that context, validates the reply (authorization + addressing rules + integrity), stores it in the Context Object, and forwards it to everyone currently authorized.
  4. \"Currently authorized\" is the Thread Policy - the to/cc of the root post as the Context Authority last published it. If the author later tightens or loosens visibility, the Context Authority republishes the root object via Update, and future activities are routed to the new audience.
"},{"location":"fep/8c13/#why-integrity-proofs-fep-8b32","title":"Why integrity proofs (FEP-8b32)","text":"

When the Context Authority forwards a reply, it may need to rewrite the reply's to/cc so that delivery matches the current Thread Policy (for example, the audience changed since the reply was written). Plain HTTP Signatures only prove who delivered a message, not who wrote it, and they break the moment an intermediary touches the payload.

So each sender attaches an Author Proof: a Data Integrity proof (FEP-8b32) computed over the activity with to/cc removed. This applies to every kind of context activity - a reply, a like, a reaction, an announce, an edit, a delete - not just posts. Excluding the addressing lets the Context Authority rewrite delivery without invalidating the author's signature over the content, while recipients can still verify that the content was written by the claimed author and not altered in transit. An optional Forwarding Proof lets the Context Authority additionally sign the addressing it chose, so recipients can verify the routing offline instead of trusting only the transport.

"},{"location":"fep/8c13/#backward-compatibility","title":"Backward compatibility","text":"

The context field and the proofs are additive: servers that don't understand them ignore them harmlessly. Such legacy servers can still receive forwarded replies and reply into a thread (their reply reaches the directly addressed author), but they cannot originate or relay integrity-protected context fan-out.

"},{"location":"fep/8c13/#applicability-across-visibility-classes","title":"Applicability across visibility classes","text":"

The same machinery serves every conversation visibility. For direct threads (addressed to explicit actors) and followers-only / private-group threads, the audience is bounded and the Context Authority forwards to it directly. For public threads the audience is unbounded, so the Context Authority pushes to the thread's participants (everyone who has replied or reacted) plus the root author's followers, and serves all other readers by letting them pull the thread from the Context Object. In every class the participants - including legacy servers - converge on the same complete reply tree. See Thread Visibility Classes.

"},{"location":"fep/8c13/#motivation","title":"Motivation","text":""},{"location":"fep/8c13/#the-reply-visibility-trap","title":"The reply visibility trap","text":"

In current ActivityPub deployments, followers-only conversations fragment:

"},{"location":"fep/8c13/#lack-of-object-level-integrity","title":"Lack of object-level integrity","text":"

HTTP Signatures authenticate transport, not content. When inbox forwarding is used, recipients cannot verify that the object content they receive was authored by the claimed actor and was not modified by an intermediate server. FEP-8b32 introduces per-object integrity proofs that decouple content verification from transport; this proposal builds on them.

A server that does not implement FEP-8b32 cannot participate in context fan-out as a sender (its activities will not be forwarded), but may still receive forwarded activities and reply as a legacy endpoint.

"},{"location":"fep/8c13/#related-work","title":"Related work","text":"

FEP-1b12 (Group Federation) established the pattern of a central actor (the Group) receiving activities and redistributing them to members, which inspired the Context Authority model. FEP-1b12 targets explicit group membership rather than ad-hoc conversations.

FEP-171b (Conversation Containers) defines a model where a single conversation owner distributes approved activities to participants via wrapper activities. That approach and this proposal address the same class of problems with different authority and delivery semantics.

Dimension FEP-8c13 (Context Authority Routing) FEP-171b (Conversation Containers) Core mechanism Native activities routed via context field Owner republishes activities via Add wrapper Authority model Context Authority validates and forwards eligible replies Conversation owner explicitly approves and commits events Commit semantics Rule-based inclusion (authorization + integrity) Explicit owner approval (Add) Wire format Standard ActivityPub activities Add(Activity) wrapper required Integrity model Mandatory Data Integrity proof for context fan-out Proof optional; origin-fetch fallback allowed Mixed environments Designed to degrade safely with legacy servers Assumes shared container semantics Adoption surface Requires FEP-8b32; reuses existing AP fields Higher: introduces new behavioral contract"},{"location":"fep/8c13/#terminology","title":"Terminology","text":""},{"location":"fep/8c13/#data-model","title":"Data Model","text":""},{"location":"fep/8c13/#context-object","title":"Context Object","text":"

The context property of an ActivityPub object MUST be an IRI identifying a dereferenceable Context Object: an authorized GET returns an ActivityPub representation, while unauthorized requests MAY receive 401/403 under the access control below.

The context property MUST be treated as a first-class payload reference, not an opaque identifier. The Context Authority MUST resolve it to obtain authoritative metadata about the thread; other servers SHOULD resolve it when they need backfill or authorization decisions.

The Context Object is authoritative for thread history indexing and backfill discovery only. Authorization (\"who may receive content\") is defined separately by the root object's current to/cc (the Thread Policy) plus local policy. The Context Object does not encode membership; it indexes the thread's Context Activities for backfill and convergence.

A Context Object URI MUST be stable and SHOULD be derivable. A simple, recommended construction is https://{context-authority}/contexts/{topLevelPostId}, which allows deterministic discovery of the context given the root object.

"},{"location":"fep/8c13/#root-authority-alignment-normative","title":"Root Authority Alignment (Normative)","text":"

For a context to be valid under this FEP, the Context Authority MUST be the same instance that hosts the root object (the object whose id is the canonical top-level post for the thread), and MUST be able to authoritatively publish Update activities for that root object.

This ensures that the entity controlling the Context Object is the same entity that can change the Thread Policy via root object updates.

The Context Object:

Example:

{\n  \"id\": \"https://alice.example/contexts/12345\",\n  \"type\": \"OrderedCollection\",\n  \"totalItems\": 5,\n  \"first\": \"https://alice.example/contexts/12345?page=1\"\n}\n
"},{"location":"fep/8c13/#collection-contents-normative","title":"Collection Contents (Normative)","text":"

When the context resolves to an OrderedCollection, that collection:

"},{"location":"fep/8c13/#access-control-for-limited-visibility-contexts","title":"Access Control for Limited-Visibility Contexts","text":"

For restricted conversations (followers-only, direct, etc.), the Context Authority MAY allow dereferencing of the Context Object and its collection pages, subject to strict access control. Authorization for such dereferencing MUST be evaluated by the Context Authority using the current Thread Policy plus local policy.

A Context Authority MAY decline remote dereferencing entirely (always returning 401/403) and rely exclusively on inbox delivery and forwarding for propagation, while still satisfying the dereferenceability requirement for local processing and authorized local actors.

These access controls apply both to the Context Object itself and to any collection pages or backfill endpoints that enumerate its Context Activities.

"},{"location":"fep/8c13/#authenticated-context-dereference-restricted-contexts","title":"Authenticated Context Dereference (Restricted Contexts)","text":"

Dereferencing a restricted Context Object MUST be authenticated by an actor-bound signature (authorized fetch); instance-only signatures MUST be rejected. Because federation trust is instance-mediated this cannot guarantee user-scoped enforcement - a server may proxy access to its own users - so a Context Authority MAY additionally require an actor authorized under the current Thread Policy, with logging, rate limits, and auditing. This is distinct from the instance-actor authentication used for forwarded deliveries.

"},{"location":"fep/8c13/#context-activities-and-integrity-proofs","title":"Context Activities and Integrity Proofs","text":"

When a Context Activity is ingested, its context reference MUST be resolved to the Context Object and associated with it, not with the immediate parent (inReplyTo) alone. Servers MUST NOT treat context as purely informational; it defines authoritative thread context for history and lifecycle.

This applies to every activity type that participates in the thread - Create, Update, Delete, Like, EmojiReact, Announce, Undo, etc. - not only to reply posts. Many of these (e.g. Like, EmojiReact, Announce) carry their target as an IRI in object rather than an embedded object; for those, context and the Author Proof are carried on the activity itself.

When generating a Context Activity for an object with a resolvable context, implementations:

"},{"location":"fep/8c13/#effective-context-iri-normative","title":"Effective Context IRI (Normative)","text":"

If both a legacy Linked Data signature and a Data Integrity proof are present, implementations MUST ignore the legacy signature for object integrity.

All Context Activities intended for context fan-out MUST carry a valid Author Proof.

"},{"location":"fep/8c13/#json-ld-context-and-extension-terms-normative","title":"JSON-LD Context and Extension Terms (Normative)","text":"

authorProof and forwardingProof are not defined by the ActivityStreams 2.0 context. Activities carrying them SHOULD include an @context defining them, alongside the Data Integrity context from the deployment's FEP-8b32 profile, so JSON-LD processors do not drop them:

\"@context\": [\n  \"https://www.w3.org/ns/activitystreams\",\n  \"https://w3id.org/security/data-integrity/v1\",\n  \"https://w3id.org/fep/8c13\"\n]\n

The https://w3id.org/fep/8c13 term context (provisional; to be assigned on publication) defines authorProof and forwardingProof as DataIntegrityProof containers. The Data Integrity context MUST match the one used by the deployment's FEP-8b32 profile.

Because eddsa-jcs-2022 canonicalizes the JSON document with JCS - including @context - signers and verifiers MUST use the same @context; it is part of the Author Proof signed input and is not among the excluded fields. Implementations that do not perform JSON-LD processing MAY treat authorProof/forwardingProof as plain JSON members but MUST still reproduce the exact @context for canonicalization. The wire examples elsewhere in this document show only the ActivityStreams context for brevity; conforming activities include the full @context above.

"},{"location":"fep/8c13/#author-proof-canonicalization-normative","title":"Author Proof Canonicalization (Normative)","text":"

The Author Proof uses the canonicalization and verification rules of FEP-8b32, with one addition defined here: certain addressing and forwarding fields are excluded from the signed input. The Author Proof is carried in a dedicated authorProof field (not proof); for the FEP-8b32 process, authorProof is the proof container.

Before canonicalization, the signer and all verifiers MUST remove the following fields from the activity and from any embedded object: to, cc, and forwardingProof. The legacy Linked Data signature field MUST also be excluded if present. No other fields may be excluded for Author Proof verification under this FEP. The removed fields are treated as not part of the signed input.

The proof being verified is excluded as defined by FEP-8b32 (verifiers canonicalize the document without the authorProof value they are verifying, and process its proof options without proofValue); this FEP does not alter that base rule, only the field name.

"},{"location":"fep/8c13/#relationship-to-fep-8b32-normative","title":"Relationship to FEP-8b32 (Normative)","text":"

The Author Proof reuses the FEP-8b32 proof envelope and cryptosuite (e.g. eddsa-jcs-2022), but it is computed over a transformed input: the activity with to, cc, and forwardingProof removed. This exclusion is deliberate - it is precisely what lets the Context Authority rewrite delivery addressing to match the current Thread Policy without invalidating the author's signature over the content.

FEP-8b32 and the eddsa-jcs-2022 cryptosuite sign the whole document (minus the proof being verified) and have no field-exclusion step. So an Author Proof is intentionally not a whole-document FEP-8b32 proof. To keep the two from being confused, the Author Proof is carried in a dedicated authorProof field rather than the standard proof field. This is what keeps the design compatible with FEP-8b32:

In short, authorProof and proof are independent layers: authorProof provides author-authenticity that survives address rewiring, while proof (if present) provides ordinary whole-document integrity. A deployment may use either or both.

Because a whole-document proof signs to/cc (which the Author Proof deliberately excludes), such a proof is invalidated whenever the Context Authority rewires addressing during forwarding. For context-routed activities the Author Proof is therefore the authoritative content-integrity check. A whole-document proof on a context-routed activity or its embedded object MUST NOT be relied upon to survive forwarding, and recipients MUST NOT reject a context-routed activity solely because such a proof fails to verify; rejection is governed by the Author Proof and the Routing Decision Matrix. Senders that need content to remain verifiable across forwarding SHOULD rely on the Author Proof rather than a whole-document proof.

"},{"location":"fep/8c13/#forwarding-proof-canonicalization-normative-optional","title":"Forwarding Proof Canonicalization (Normative, Optional)","text":"

The Forwarding Proof input MUST be a JSON object with exactly these keys:

If the activity embeds its object, object is the embedded object's id; if object is an IRI, object is that IRI; if neither is available, a Forwarding Proof MUST NOT be generated.

Absent to/cc are treated as empty arrays. If present, they MUST be arrays of IRI strings; any other form (string singleton, object, non-IRI value) MUST cause Forwarding Proof generation or verification to fail. The to and cc arrays MUST be normalized by removing duplicates (set semantics) and sorting lexicographically by Unicode code points of the IRI string.

The resulting JSON object MUST be serialized using JSON Canonicalization Scheme (JCS, RFC 8785) before generating the Data Integrity proof.

"},{"location":"fep/8c13/#proof-placement-normative","title":"Proof Placement (Normative)","text":""},{"location":"fep/8c13/#integrity-requirements-normative","title":"Integrity Requirements (Normative)","text":""},{"location":"fep/8c13/#example-incoming-reply-sender-context-authority","title":"Example: Incoming Reply (Sender \u2192 Context Authority)","text":"

The sender addresses the reply to match the Thread Policy (followers-only here). The Author Proof is computed with to/cc excluded from the canonicalized input.

{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"id\": \"https://bob.example/activities/98765\",\n  \"type\": \"Create\",\n  \"actor\": \"https://bob.example/u/bob\",\n  \"to\": [\"https://alice.example/u/alice/followers\"],\n  \"cc\": [\"https://alice.example/u/alice\"],\n  \"object\": {\n    \"id\": \"https://bob.example/posts/98765\",\n    \"type\": \"Note\",\n    \"attributedTo\": \"https://bob.example/u/bob\",\n    \"inReplyTo\": \"https://alice.example/posts/12345\",\n    \"context\": \"https://alice.example/contexts/12345\",\n    \"content\": \"Hi Alice, I saw your followers-only post.\",\n    \"to\": [\"https://alice.example/u/alice/followers\"],\n    \"cc\": [\"https://alice.example/u/alice\"]\n  },\n  \"authorProof\": {\n    \"type\": \"DataIntegrityProof\",\n    \"cryptosuite\": \"eddsa-jcs-2022\",\n    \"created\": \"2026-01-16T15:20:45Z\",\n    \"verificationMethod\": \"https://bob.example/u/bob#main-key\",\n    \"proofPurpose\": \"assertionMethod\",\n    \"proofValue\": \"z3FXQjecWuf...\"\n  }\n}\n
"},{"location":"fep/8c13/#example-forwarded-reply-context-authority-recipient","title":"Example: Forwarded Reply (Context Authority \u2192 Recipient)","text":"

The Context Authority forwards the activity (no rewiring needed in this example). The Author Proof is unchanged (it was computed without addressing fields). The optional Forwarding Proof is shown, signed by the Context Authority's instance actor.

{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"id\": \"https://bob.example/activities/98765\",\n  \"type\": \"Create\",\n  \"actor\": \"https://bob.example/u/bob\",\n  \"to\": [\"https://alice.example/u/alice/followers\"],\n  \"cc\": [\"https://alice.example/u/alice\"],\n  \"object\": {\n    \"id\": \"https://bob.example/posts/98765\",\n    \"type\": \"Note\",\n    \"attributedTo\": \"https://bob.example/u/bob\",\n    \"inReplyTo\": \"https://alice.example/posts/12345\",\n    \"context\": \"https://alice.example/contexts/12345\",\n    \"content\": \"Hi Alice, I saw your followers-only post.\",\n    \"to\": [\"https://alice.example/u/alice/followers\"],\n    \"cc\": [\"https://alice.example/u/alice\"]\n  },\n  \"authorProof\": {\n    \"type\": \"DataIntegrityProof\",\n    \"cryptosuite\": \"eddsa-jcs-2022\",\n    \"created\": \"2026-01-16T15:20:45Z\",\n    \"verificationMethod\": \"https://bob.example/u/bob#main-key\",\n    \"proofPurpose\": \"assertionMethod\",\n    \"proofValue\": \"z3FXQjecWuf...\"\n  },\n  \"forwardingProof\": {\n    \"type\": \"DataIntegrityProof\",\n    \"cryptosuite\": \"eddsa-jcs-2022\",\n    \"created\": \"2026-01-16T15:21:00Z\",\n    \"verificationMethod\": \"https://alice.example/actor#main-key\",\n    \"proofPurpose\": \"assertionMethod\",\n    \"proofValue\": \"z4HYRkemXvg...\"\n  }\n}\n
"},{"location":"fep/8c13/#example-like-non-create-activity","title":"Example: Like (non-Create activity)","text":"

Context routing is not limited to posts. Likes, reactions, announces, edits, and deletes are Context Activities too. A Like carries its target as an IRI in object (no embedded object), so context and the authorProof are carried on the activity. The Author Proof is still computed with to/cc excluded.

{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"id\": \"https://bob.example/activities/55512\",\n  \"type\": \"Like\",\n  \"actor\": \"https://bob.example/u/bob\",\n  \"to\": [\"https://alice.example/u/alice/followers\"],\n  \"cc\": [\"https://alice.example/u/alice\"],\n  \"context\": \"https://alice.example/contexts/12345\",\n  \"object\": \"https://alice.example/posts/12345\",\n  \"authorProof\": {\n    \"type\": \"DataIntegrityProof\",\n    \"cryptosuite\": \"eddsa-jcs-2022\",\n    \"created\": \"2026-01-16T15:25:10Z\",\n    \"verificationMethod\": \"https://bob.example/u/bob#main-key\",\n    \"proofPurpose\": \"assertionMethod\",\n    \"proofValue\": \"z5KZ8nQ2tps...\"\n  }\n}\n
"},{"location":"fep/8c13/#addressing-and-context-routing-semantics","title":"Addressing and Context-Routing Semantics","text":""},{"location":"fep/8c13/#signaling-intent-to-use-context-routing","title":"Signaling Intent to Use Context Routing","text":"

When constructing a Context Activity for context-audience routing, the sender:

When requesting context routing, senders MUST deliver the Context Activity to the inbox (or endpoints.sharedInbox) of the root object's author - i.e. the Context Authority instance per Root Authority Alignment. The root object's attributedTo actor's inbox is the discovery target.

A receiving server treats an activity as requesting context routing only when both:

This specification requires no capability discovery or negotiation. Routing intent is expressed solely by the presence of an Effective Context IRI in the payload; whether routing occurs additionally depends on the receiver being the Context Authority and on the validation requirements below.

"},{"location":"fep/8c13/#normative-meaning-of-effective-context-iri","title":"Normative Meaning of Effective Context IRI","text":"

If an activity carries an Effective Context IRI and the receiving server is the Context Authority for it, the activity is requesting (a) ingestion into the thread as a Context Activity, and (b) potential forwarding to authorized recipients, subject to the validation and integrity requirements below.

The presence of an Effective Context IRI is necessary but not sufficient for context routing. Eligibility is determined exclusively by a valid Author Proof and authorization checks - never by the perceived capabilities of the sender's server.

No capability negotiation (normative):

"},{"location":"fep/8c13/#reply-visibility-rules","title":"Reply Visibility Rules","text":"

The Context Authority stores the Thread Policy as the root object's to/cc. An activity's effective visibility is determined by comparing its Effective Addressing against the root object's current to/cc.

These rules are written in terms of replies for readability, but apply to all Context Activities (replies, reactions, edits, deletes, etc.).

Inherited (\"follow-post\") addressing: Effective Addressing exactly equal (set-equality, order-insensitive) to the root object's current to/cc. This signals \"same visibility as thread.\"

Narrowed addressing: An activity MAY narrow visibility using any addressing forms permitted by ActivityPub (individual actor IRIs and/or collections), provided the resulting Effective Addressing is not more permissive than the current Thread Policy and is a subset of the Authorized Recipient Set under the current Thread Policy and local policy.

The Context Authority MUST evaluate whether the narrowed Effective Addressing is a subset of the Authorized Recipient Set, including membership in collections it controls (e.g. followers). If it cannot determine subset safety (e.g. unknown external collection semantics), it MUST treat the activity as more permissive and MUST reject it for context routing.

For narrowed activities, the Context Authority MUST preserve the sender's narrowed Effective Addressing when forwarding, except to remove recipients no longer authorized under the current Thread Policy and local policy. It MUST NOT widen a narrowed activity to the full Thread Policy.

Prohibited addressing: An activity MUST NOT be more permissive than the Thread Policy. Violations MUST be rejected by the Context Authority.

Recipient set equality: Two addressing fields are equal if they contain the same IRIs as a set, ignoring order and duplicates. Implementations MUST canonicalize lists before comparison by removing duplicates and sorting lexicographically by Unicode code points. Comparison is string-based only: implementations MUST NOT dereference IRIs or apply semantic equivalence when comparing recipients.

"},{"location":"fep/8c13/#thread-visibility-classes","title":"Thread Visibility Classes","text":"

This FEP applies to conversations of any visibility, but it separates two notions that the rest of this document treats together for restricted threads:

The Delivery Target is a set of delivery inboxes, not an addressing instruction. Forwarded to/cc continue to follow the Thread Policy and the Address Rewiring rules; the Context Authority MUST NOT enumerate the Delivery Target into the forwarded to/cc (see Security Considerations).

Three visibility classes are distinguished by the root object's addressing:

Class Thread Policy (root to/cc) Authorized Recipient Set Delivery Target (push) Backfill (pull) Direct Explicit actor IRIs The listed actors (enumerable) = Authorized Recipient Set Authorized actors Followers-only / private group A collection (e.g. followers) Collection members (may be non-enumerable) = Authorized Recipient Set Authorized actors Public Includes as:Public Everyone (unbounded) Participation Set + root author's followers Anyone (public Context Object)

Followers-only and private-group threads are the default case described throughout this document: authorization and delivery coincide, and the Context Authority forwards to the Authorized Recipient Set, expanding collections it controls. The two subsections below add the rules for the direct and public classes.

"},{"location":"fep/8c13/#direct-conversations-normative","title":"Direct Conversations (Normative)","text":"

When the Thread Policy is a set of explicit actor IRIs (a direct message or an enumerated private group), the Authorized Recipient Set and the Delivery Target are both that explicit actor set, so subset checks under the Reply Visibility Rules are exact: a reply addressing any actor outside the set - or as:Public - is more permissive than the Thread Policy and MUST be rejected, which is what stops a participant from leaking the thread by replying broadly. Membership is changed by the root author updating the root object's to/cc, propagated via Root Policy Update Propagation.

The Author Proof is the primary defense against the Context Authority fabricating a message attributed to another participant; recipients MUST reject forwarded activities whose Author Proof does not verify against the activity actor. Routing through one instance still lets it withhold or reorder messages (forgery is prevented, censorship is not), and this FEP provides no end-to-end confidentiality - direct content is visible to the Context Authority in cleartext, as with ordinary ActivityPub direct messages.

"},{"location":"fep/8c13/#example-direct-reply","title":"Example: Direct Reply","text":"

Alice's root post is addressed to the explicit member set {bob, carol} (the Thread Policy). Bob replies; his Effective Addressing {alice, carol} is a subset, so the Context Authority forwards it to Alice and Carol. Adding a member later is just an Update to the root to/cc, after which the new actor's server backfills from the Context Object.

{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"id\": \"https://bob.example/activities/3002\",\n  \"type\": \"Create\",\n  \"actor\": \"https://bob.example/u/bob\",\n  \"to\": [\n    \"https://alice.example/u/alice\",\n    \"https://carol.example/u/carol\"\n  ],\n  \"object\": {\n    \"id\": \"https://bob.example/posts/3002\",\n    \"type\": \"Note\",\n    \"attributedTo\": \"https://bob.example/u/bob\",\n    \"inReplyTo\": \"https://alice.example/posts/3001\",\n    \"context\": \"https://alice.example/contexts/3001\",\n    \"content\": \"Works for me.\",\n    \"to\": [\n      \"https://alice.example/u/alice\",\n      \"https://carol.example/u/carol\"\n    ]\n  },\n  \"authorProof\": {\n    \"type\": \"DataIntegrityProof\",\n    \"cryptosuite\": \"eddsa-jcs-2022\",\n    \"created\": \"2026-01-16T17:02:11Z\",\n    \"verificationMethod\": \"https://bob.example/u/bob#main-key\",\n    \"proofPurpose\": \"assertionMethod\",\n    \"proofValue\": \"z6PkR2mWq8t...\"\n  }\n}\n
"},{"location":"fep/8c13/#public-conversations-normative","title":"Public Conversations (Normative)","text":"

When the Thread Policy includes the public sentinel (https://www.w3.org/ns/activitystreams#Public, as:Public), every actor is authorized, so the Reply Visibility Rules permit any addressing. A reply MAY still narrow its own visibility (e.g. a followers-only reply to a public post), which the Context Authority MUST preserve and MUST NOT broaden back to Public.

Because the audience is unbounded, delivery is split between push and pull. The Context Authority maintains a Participation Set - the root author plus every actor whose Context Activity it has ingested (optionally limited to authors of Create activities, to curb amplification) - and SHOULD forward each ingested activity to the other participants and to the root author's followers, deduplicated by sharedInbox. The forwarded to/cc stay as the Thread Policy addressing (as:Public plus followers); the Participation Set only selects inboxes and MUST NOT be enumerated into the addressing. Everyone else - readers who never engage - obtains the thread by pull, dereferencing the (publicly readable) Context Object; a new participant backfills the same way on its first reply, then receives the rest by push. On large threads the Context Authority MAY rate-limit, drop high-volume reactions from push, or fall back to pull-only, provided the Context Object stays dereferenceable for backfill.

Because forwarded activities are native (Create, Like, etc.) with ordinary addressing, legacy servers among the participants receive them as normal federated replies and gain a more complete thread with no code changes - which is how this FEP closes the well-known fragmentation of public reply trees. (informative)

"},{"location":"fep/8c13/#example-public-thread-fan-out","title":"Example: Public Thread Fan-Out","text":"

Bob and Carol have already replied to Alice's public post, so the Participation Set is {alice, bob, carol}. Dave replies publicly, delivering to Alice's inbox:

{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"id\": \"https://dave.example/activities/4004\",\n  \"type\": \"Create\",\n  \"actor\": \"https://dave.example/u/dave\",\n  \"to\": [\"https://www.w3.org/ns/activitystreams#Public\"],\n  \"cc\": [\n    \"https://alice.example/u/alice/followers\",\n    \"https://alice.example/u/alice\"\n  ],\n  \"object\": {\n    \"id\": \"https://dave.example/posts/4004\",\n    \"type\": \"Note\",\n    \"attributedTo\": \"https://dave.example/u/dave\",\n    \"inReplyTo\": \"https://alice.example/posts/4000\",\n    \"context\": \"https://alice.example/contexts/4000\",\n    \"content\": \"Jumping in late - great thread.\",\n    \"to\": [\"https://www.w3.org/ns/activitystreams#Public\"],\n    \"cc\": [\n      \"https://alice.example/u/alice/followers\",\n      \"https://alice.example/u/alice\"\n    ]\n  },\n  \"authorProof\": {\n    \"type\": \"DataIntegrityProof\",\n    \"cryptosuite\": \"eddsa-jcs-2022\",\n    \"created\": \"2026-01-16T18:40:03Z\",\n    \"verificationMethod\": \"https://dave.example/u/dave#main-key\",\n    \"proofPurpose\": \"assertionMethod\",\n    \"proofValue\": \"z7QmN4rXc9v...\"\n  }\n}\n
"},{"location":"fep/8c13/#context-authority-processing","title":"Context Authority Processing","text":"

When a Context Authority receives an activity whose Effective Context IRI references a local Context Object, it MUST process it per the steps below. Authorization to submit and to deliver is decided by the Context Authority using the current Thread Policy (root to/cc) plus local rules (blocks, mutes).

"},{"location":"fep/8c13/#validation","title":"Validation","text":"

The Context Authority MUST:

  1. Resolve the Effective Context IRI and confirm it is a Context Object controlled by a local actor (per Root Authority Alignment).
  2. Authenticate the sender via transport (HTTP Signatures or equivalent) and map the request to an ActivityPub actor.
  3. Check sender authorization to submit Context Activities, informed by the current Thread Policy and local policy.
  4. Verify the activity's addressing conforms to the Reply Visibility Rules. Subset evaluation MUST use locally available state for collections it controls (including followers) and any local policy inputs.
  5. Verify the Author Proof: canonicalize with the Author Proof exclusions, then verify against the referenced verification method (which MUST belong to the activity's actor). An Author Proof is verified iff present and successfully verified.

The Context Authority then applies the Routing Decision Matrix. In particular:

"},{"location":"fep/8c13/#transport-authentication-normative","title":"Transport Authentication (Normative)","text":""},{"location":"fep/8c13/#routing-decision-matrix-normative","title":"Routing Decision Matrix (Normative)","text":"

Context-audience routing MUST be integrity-protected: the Context Authority MUST NOT forward any Context Activity unless it carries a verified Author Proof. This is absolute and independent of the sender's implementation or perceived capabilities. The Context Authority MUST determine routing solely from the activity payload and local authorization policy:

Effective Context IRI Author Proof Sender Authorized Required Behavior No Any Any Treat as ordinary ActivityPub delivery Yes Absent Any MAY deliver to directly addressed local recipients; MUST NOT ingest; MUST NOT forward Yes Present but invalid Any MUST reject Yes Present and verified No MUST reject Yes Present and verified Yes MUST ingest and MUST forward (rewiring as needed; MAY add a Forwarding Proof)

No additional server capability checks or negotiations are permitted or required.

"},{"location":"fep/8c13/#ingestion","title":"Ingestion","text":"

For Context Activities from authorized senders with a verified Author Proof, the Context Authority MUST persist the activity/object and MUST add the activity ID to the Context Object's collection.

Activities lacking a valid Author Proof MAY be delivered to directly addressed local recipients (subject to local policy) but MUST NOT be added to the Context Object's collection and are ineligible for context-audience routing.

"},{"location":"fep/8c13/#forwarding","title":"Forwarding","text":"

For Context Activities eligible for context-audience routing, the Context Authority:

Forwarding MUST NOT depend on the perceived compliance or capabilities of recipient servers. Expanding a local follower collection to its remote inboxes does not require rewriting the activity's addressing.

"},{"location":"fep/8c13/#address-rewiring-normative","title":"Address Rewiring (Normative)","text":"

The Context Authority MUST NOT rewrite to/cc to enumerate individual actor IRIs solely to target specific recipients.

The Context Authority MAY rewrite to/cc to make forwarded deliveries conform to the current Thread Policy, including when:

If the sender's addressing is more permissive than the current Thread Policy, the activity MUST be rejected.

When forwarding an embedded-object activity, rewiring MUST be applied consistently:

"},{"location":"fep/8c13/#content-immutability-normative","title":"Content Immutability (Normative)","text":"

The Context Authority MUST NOT modify any field included in the Author Proof input. Any forwarder mutation MUST be limited to the excluded fields (to, cc, forwardingProof). If any other modification would be required, the Context Authority MUST reject the activity for context routing (it MAY still deliver it locally to directly addressed recipients per legacy behavior).

"},{"location":"fep/8c13/#transport-authentication-for-forwarded-deliveries-normative","title":"Transport Authentication for Forwarded Deliveries (Normative)","text":"

A recipient implementing this FEP MUST authenticate forwarded deliveries via HTTP Signatures (or equivalent) and MUST confirm the request is verifiable using a verification method controlled by the Context Authority's instance actor for the referenced Context Object. This transport-level check is the baseline for accepting forwarded activities: it ensures any addressing updates were performed by the Context Authority without requiring payload-level proofs.

"},{"location":"fep/8c13/#payloadtransport-binding-normative-best-effort","title":"Payload/Transport Binding (Normative, Best-Effort)","text":"

Recipients SHOULD require a request signature profile that binds the request body (e.g. via the Digest header, or Content-Digest/Repr-Digest per RFC 9530) when available. If body binding is unavailable, recipients MAY accept forwarded deliveries but MUST treat addressing updates as transport-trusted rather than payload-verifiable.

"},{"location":"fep/8c13/#optional-forwarding-proof-normative-optional","title":"Optional Forwarding Proof (Normative, Optional)","text":"

Implementations MAY attach and verify a Forwarding Proof that cryptographically binds the forwarded to/cc to the Context Authority, enabling offline verification and reducing reliance on transport-layer body binding. It provides defense-in-depth: even if transport authentication is compromised, the payload-level proof shows the addressing was authorized by the Context Authority.

When a Context Authority attaches a Forwarding Proof, it MUST be computed per Forwarding Proof Canonicalization and placed in the forwardingProof field.

When a recipient receives a Forwarding Proof, it SHOULD verify it and MAY treat success as a stronger signal than transport authentication alone. On failure, it SHOULD fall back to transport authentication and MAY log the failure.

"},{"location":"fep/8c13/#recipient-processing-normative","title":"Recipient Processing (Normative)","text":"

When a server implementing this FEP receives a context-routed activity, it MUST:

  1. Verify transport authentication. Confirm the HTTP request is verifiable using a verification method controlled by the Context Authority's instance actor for the referenced Context Object - the instance actor sharing the origin of the Context Object id (Root Authority Alignment). A forwarded delivery whose signer is not controlled by that origin MUST be rejected. On failure, MUST reject.
  2. Verify the Author Proof. Canonicalize with the Author Proof exclusions, then verify authorProof against the activity actor's verification method. On failure, MUST reject.
  3. Verify the Forwarding Proof if present. Recipients SHOULD verify it chains to the Context Authority's instance-actor key for the resolved Context Object and binds to the Author Proof (via the committed authorProofValue). On success, treat addressing updates as payload-verified; on failure, MAY fall back to transport-authenticated delivery (and MAY log).
  4. Accept. If transport authentication passes and the Author Proof verifies, accept the activity, subject to local policy (blocks, mutes, etc.).

No additional recipient-side authorization checks are required: the Context Authority's transport-authenticated delivery implies the recipient is in the authorized audience. Recipients treat the Context Authority as the policy oracle for delivery authorization, and MUST evaluate visibility/presentation using Effective Addressing.

"},{"location":"fep/8c13/#root-policy-update-propagation-normative","title":"Root Policy Update Propagation (Normative)","text":"

When the root object's addressing (to/cc) changes, the Context Authority MUST publish an Update activity whose object is the full updated root object (same id), and MUST deliver it per ordinary ActivityPub delivery.

Receiving servers implementing this FEP MUST update their stored copy of the root object upon accepting the Update, and MUST treat the updated addressing as the current Thread Policy for the context.

"},{"location":"fep/8c13/#thread-visibility-re-evaluation-normative","title":"Thread Visibility Re-evaluation (Normative)","text":"

Upon learning an updated root object addressing for a context (via Update), a receiving server implementing this FEP SHOULD re-evaluate which local users are authorized under the current Thread Policy, and SHOULD restrict presentation of previously stored context items accordingly. It MAY retain stored objects for legal, abuse, or operational reasons, but MUST treat them as not visible to unauthorized local users. For contexts that have become unauthorized for all local users, servers SHOULD minimize retention duration and metadata exposure, consistent with the deletion guidance in Context Retention and Deletion.

"},{"location":"fep/8c13/#context-discovery-and-backfill-normative","title":"Context Discovery and Backfill (Normative)","text":"

When a server begins hosting an actor that becomes part of the Context Audience after a conversation has started (e.g. Bob follows Alice mid-thread), the server SHOULD attempt to discover and backfill the context, subject to local policy, resource limits, and authorization constraints.

Discovery trigger: Discovery SHOULD be triggered when a server learns it hosts an actor authorized under the current Thread Policy for a context and has learned one or more Context Object URIs for it.

Discovery procedure:

  1. The server SHOULD attempt to dereference the Context Object URI.
  2. If access is permitted, it SHOULD retrieve the Context Object and enumerate its items.
  3. It SHOULD fetch and ingest referenced Context Activities (replies, reactions, updates, deletes, etc.), subject to local retention policy and resource limits.

Fallback (normative): If the Context Authority does not permit remote dereferencing, or dereferencing fails due to authorization or network constraints, the server MUST treat backfill as unavailable and rely solely on delivered activities. Full history convergence is then not guaranteed.

Suggested limits (non-normative): apply paging, caps on pages/items per context, and/or incremental (\"newest first\") backfill to reduce amplification risk and allow partial convergence under load.

"},{"location":"fep/8c13/#example-mid-thread-follow","title":"Example: Mid-Thread Follow","text":"

Alice creates a followers-only thread. Bob follows Alice after 5 Context Activities already exist. Bob's server performs GET https://alice.example/contexts/12345, receives an OrderedCollection with 5 items, and backfills all 5 (including non-reply events such as reactions or edits) to present a complete conversation view.

"},{"location":"fep/8c13/#context-retention-and-deletion-normative","title":"Context Retention and Deletion (Normative)","text":"

If a server reasonably determines (from locally observable state) that it no longer hosts any actor authorized under the current Thread Policy for a Context Object, it SHOULD delete its entire local copy of that context - the Context Object and all Context Activities associated exclusively with it - subject to local policy and legal/operational constraints. Implementations SHOULD apply a grace period to avoid thrash from transient authorization changes.

This prevents indefinite retention of private or restricted conversations by unrelated servers.

"},{"location":"fep/8c13/#example-audience-exhaustion","title":"Example: Audience Exhaustion","text":"

Bob and Carol both unfollow Alice; no local actors remain in Alice's Context Audience. Bob's server SHOULD delete https://alice.example/contexts/12345 and all Context Activities belonging solely to that context. If Bob later re-follows Alice, the context is rediscovered and backfilled per the previous section.

"},{"location":"fep/8c13/#context-payload-resolution","title":"Context Payload Resolution","text":"

When resolving a context URI, the receiving server MUST resolve it to a Context Object and associate Context Activities with that object. The context MUST NOT be resolved to the root Note object, an inReplyTo chain, or a transient/inferred thread identifier. The Context Object is the sole authoritative container for the conversation, ensuring consistent backfill, authorization checks, and lifecycle management across servers.

"},{"location":"fep/8c13/#design-note-non-normative","title":"Design Note (Non-Normative)","text":"

This mechanism intentionally avoids explicit capability discovery. Compliance is inferred per activity from payload semantics: an Effective Context IRI signals routing intent; a valid Author Proof signals eligibility; transport authentication (HTTP Signatures) is the baseline for trusting context-forwarded delivery; and an optional Forwarding Proof provides enhanced payload-level verification. This eliminates downgrade attacks via mixed-compliance networks and allows gradual adoption without coordination.

"},{"location":"fep/8c13/#legacy-interoperability","title":"Legacy Interoperability","text":""},{"location":"fep/8c13/#unknown-context-field-informative","title":"Unknown context Field (Informative)","text":"

Legacy servers ignore the context field, so including it is safe: it causes no extra fetches and no effect on access control.

"},{"location":"fep/8c13/#acceptance-of-legacy-objects","title":"Acceptance of Legacy Objects","text":"

A Context Authority MAY accept legacy objects (objects lacking a valid Author Proof) from legacy peers if the activity is directly addressed to a local actor and local federation policy permits. Acceptance does not imply eligibility for context routing.

"},{"location":"fep/8c13/#forwarding-restrictions-for-legacy-objects","title":"Forwarding Restrictions for Legacy Objects","text":"

Legacy activities:

This ensures recipients relying on integrity guarantees never receive unverifiable content through protected channels.

"},{"location":"fep/8c13/#replies-originating-from-legacy-servers","title":"Replies Originating from Legacy Servers","text":"

When a legacy server replies to a restricted thread, the reply MAY be visible to the directly addressed recipient (e.g. the root author) and MAY be associated with that recipient's local thread view, but MUST NOT be forwarded to the Context Audience. This reflects the limited guarantees of legacy federation while preserving conversation integrity for protected participants.

Example legacy reply (delivered as a Create):

{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"id\": \"https://legacy.example/activities/55555\",\n  \"type\": \"Create\",\n  \"actor\": \"https://legacy.example/u/charlie\",\n  \"to\": [\"https://alice.example/u/alice\"],\n  \"object\": {\n    \"id\": \"https://legacy.example/posts/55555\",\n    \"type\": \"Note\",\n    \"attributedTo\": \"https://legacy.example/u/charlie\",\n    \"inReplyTo\": \"https://alice.example/posts/12345\",\n    \"content\": \"Hello Alice!\",\n    \"to\": [\"https://alice.example/u/alice\"]\n  }\n}\n

This activity lacks an Author Proof, so it cannot be context-forwarded but may be delivered to the directly addressed recipient (Alice).

Sequence: Legacy Reply

sequenceDiagram\n    participant LegacySrv as Legacy Server\n    participant AliceSrv as Context Authority\n    participant CarolSrv as Follower\n\n    LegacySrv->>AliceSrv: Inbox POST Create{Note}\n    AliceSrv->>AliceSrv: Deliver to Alice locally (no Context Object ingest)\n    AliceSrv-x CarolSrv: No forwarding
"},{"location":"fep/8c13/#security-considerations","title":"Security Considerations","text":""},{"location":"fep/8c13/#visibility-changes-and-retroactive-tightening-informative","title":"Visibility Changes and Retroactive Tightening (Informative)","text":"

Updating the root object's addressing lets compliant implementations retroactively restrict presentation of previously stored thread items for local users no longer authorized under the updated Thread Policy. This provides no guarantee of content recall across the federation:

This limitation is inherent to federated systems: the policy update mechanism provides best-effort restriction for compliant implementations, not guaranteed erasure.

"},{"location":"fep/8c13/#references","title":"References","text":""},{"location":"fep/8c13/#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/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.com WITHDRAWN 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.

"},{"location":"fep/8c3f/#example","title":"Example","text":"
{\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://.

"},{"location":"fep/8c3f/#references","title":"References","text":""},{"location":"fep/8c3f/#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/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.com FINAL 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

"},{"location":"fep/8fcf/#partial-follower-collection-digest","title":"Partial follower collection digest","text":"

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

"},{"location":"fep/8fcf/#the-collection-synchronization-http-header","title":"The 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:

"},{"location":"fep/8fcf/#synchronization-procedure","title":"Synchronization procedure","text":""},{"location":"fep/8fcf/#on-the-sender-end","title":"On the sender end","text":"

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.

"},{"location":"fep/8fcf/#on-the-receiving-end","title":"On the receiving end","text":"

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.

"},{"location":"fep/8fcf/#implementations","title":"Implementations","text":""},{"location":"fep/8fcf/#references","title":"References","text":""},{"location":"fep/8fcf/#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/9091/","title":"FEP-9091: Export Actor Service Endpoint","text":"Authors Status Date received Tracking issue Discussions Repository Dmitri Zagidulin @dmitri@social.coop DRAFT 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):

"},{"location":"fep/9091/#example-actor-profile-with-export-service-endpoint","title":"Example Actor Profile with Export Service Endpoint","text":"
{\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:

  1. Fetch the Actor profile object.
  2. Locate the service description object with the expanded type equal to https://w3id.org/fep/9091#Export.
  3. Locate the corresponding serviceEndpoint URL, in that description object.

  4. The client makes an HTTP POST to the URL in the serviceEndpoint to initiate the export operation.

"},{"location":"fep/9091/#example-request-and-response","title":"Example Request and Response","text":"

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":""},{"location":"fep/9091/#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/9098/","title":"FEP-9098: Custom emojis","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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:

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.

"},{"location":"fep/9098/#accessibility","title":"Accessibility","text":"

The short description of a custom emoji can be specified with the alternateName property (the full IRI is http://schema.org/alternateName).

"},{"location":"fep/9098/#using-custom-emojis","title":"Using custom emojis","text":""},{"location":"fep/9098/#microsyntax","title":"Microsyntax","text":"

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.

"},{"location":"fep/9098/#web-client-considerations","title":"Web client considerations","text":"

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:

"},{"location":"fep/9098/#compatibility","title":"Compatibility","text":"

In order to be compatible with as many servers as possible, implementations SHOULD comply with the following additional requirements:

"},{"location":"fep/9098/#implementations","title":"Implementations","text":"

This document is based on implementations of custom emojis in Pleroma, Mastodon, Misskey and Fedibird.

"},{"location":"fep/9098/#references","title":"References","text":""},{"location":"fep/9098/#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/96ff/","title":"FEP-96ff: Explicit signalling of ActivityPub Semantics","text":"Authors Status Date received Tracking issue Discussions Repository Erin Shepherd fep@erinshepherd.net DRAFT 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":""},{"location":"fep/96ff/#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/9967/","title":"FEP-9967: Polls","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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:

The value of this property MUST be an array of objects representing poll options:

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.

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/#counting-voters","title":"Counting voters","text":"

The votersCount property can be used to specify how many people have voted in a poll.

This number will be distinct from the number of votes in case of multiple-choice polls, and can be used to calculate the proportion of participants who voted for a poll option.

"},{"location":"fep/9967/#limits","title":"Limits","text":"

The number of poll options is often limited, but the limit differs between implementations.

Mastodon allows up to 500 options per poll.

"},{"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:

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 is not 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:

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.

"},{"location":"fep/9967/#publishing-results","title":"Publishing results","text":"

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.

"},{"location":"fep/9967/#closing-a-poll","title":"Closing a poll","text":"

A poll is considered to be closed once the date specified by the endTime property has been reached. Implementations MAY add the closed property to explicitly specify the date when the poll was closed.

"},{"location":"fep/9967/#editing-options","title":"Editing options","text":"

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).

"},{"location":"fep/9967/#json-schemas","title":"JSON schemas","text":""},{"location":"fep/9967/#implementations","title":"Implementations","text":"

This document is based on implementations of polls in Mastodon and Pleroma.

Several projects announced support for this FEP after it was published:

"},{"location":"fep/9967/#references","title":"References","text":""},{"location":"fep/9967/#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/9f9f/","title":"FEP-9f9f: Collections","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT informational 2026-04-03 #810 Discussions codeberg"},{"location":"fep/9f9f/#summary","title":"Summary","text":"

This document describes the current best practices for implementing and using ActivityPub collections.

Some parts of it are based on the implementation of collections in GoActivityPub.

"},{"location":"fep/9f9f/#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/9f9f/#what-is-a-collection","title":"What is a collection?","text":""},{"location":"fep/9f9f/#activity-streams-definition","title":"Activity Streams definition","text":"

Activity Streams 2.0: A collection is an object with Collection type or its subtype (e.g. OrderedCollection).

"},{"location":"fep/9f9f/#duck-typing","title":"Duck typing","text":"

FEP-2277: A collection is an object with items, orderedItems, totalItems, partOf, first, last, next, prev or current property.

"},{"location":"fep/9f9f/#ownership","title":"Ownership","text":"

A collection SHOULD have an attributedTo property indicating the actor that created it.

"},{"location":"fep/9f9f/#creating-collections","title":"Creating collections","text":"

Clients can create collections by publishing a Create activity where object is a collection. The server MUST ignore reserved collection properties such as items, totalItems and pagination properties.

Some special collections, such as inbox and outbox are created automatically by a server.

Servers MAY create collections automatically when the object of Create activity contains a property that points to a collection, such as likes, shares or replies. When the specified collection ID is local, the server MUST verify that the ID is valid, and MAY assign a different ID.

Clients MUST NOT attempt to create new collections by embedding non-anonymous collections in objects.

Collection IDs SHOULD NOT contain query parameters.

"},{"location":"fep/9f9f/#updating-collections","title":"Updating collections","text":"

The contents of a collection (its items) are modified either directly by Add, Remove and Move activities, or indirectly by side-effects of other activities. If a collection has an updated property, its value SHOULD be changed whenever the contents are modified.

A client can update other properties of a collection by generating an Update activity. The server MUST NOT overwrite reserved collection properties such as items, totalItems and pagination properties.

Note

Update of a collection is not compatible with client-side signing (FEP-ae97) because partial updates are not supported there.

"},{"location":"fep/9f9f/#ordering","title":"Ordering","text":"

ActivityPub states that an OrderedCollection MUST be presented in reverse chronological order. In practice, however, some ordered collections are presented in forward chronological order, and an erratum was proposed that limits this requirement to collections defined in the ActivityPub specification (inbox, outbox, followers, following, liked, likes and shares).

"},{"location":"fep/9f9f/#filtering","title":"Filtering","text":"

Collections can be filtered.

Filters are specified using query parameters that are appended to a collection ID. A set of guidelines for using these parameters has been proposed in FEP-6606: ActivityPub client to server collections addressing conventions.

"},{"location":"fep/9f9f/#pagination","title":"Pagination","text":"

Pagination of collections is described in Activity Streams 2.0.

Collection page IDs are usually created by appending query parameters to a collection ID:

"},{"location":"fep/9f9f/#access-control","title":"Access control","text":"

Collection items MUST be filtered based on the permissions of an actor that tries to access the collection. If the actor is not authorized, only public items are returned.

A collection itself often doesn't have an audience and is treated as public by default. However, if the audience is specified, regular access control procedures MUST be used.

"},{"location":"fep/9f9f/#addressing","title":"Addressing","text":"

An object can be addressed to a collection. When determining delivery targets and regulating access to an object, the server SHOULD expand collections by replacing them with actors they contain (if any).

"},{"location":"fep/9f9f/#public-collection","title":"Public collection","text":"

https://www.w3.org/ns/activitystreams#Public is a special collection ID that is used for public addressing. It is described in ActivityPub.

"},{"location":"fep/9f9f/#references","title":"References","text":""},{"location":"fep/9f9f/#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/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.uk DRAFT 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:

"},{"location":"fep/9fde/#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/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:

  1. A lot of work for each client
  2. Something that every client needs to do
  3. Easy to get wrong
  4. Doesn't scale across multitudes of different servers
"},{"location":"fep/9fde/#no-standard-way-for-mastodon-servers-to-advertise-that-some-functionality-is-disabled-or-unavailable","title":"No standard way for Mastodon servers to advertise that some functionality is disabled or unavailable","text":"

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.

  1. There is no simple way for clients to know which parts of the Mastodon API the server supports
  2. There is no simple way for clients to know if the server supports additional operations
  3. If Mastodon decides to implement an API that was first introduced in a Mastodon-like server there is no way for clients to detect this, without recompiling the client with new information about what features a given Mastodon server version implements
"},{"location":"fep/9fde/#server-developers-have-too-much-to-do","title":"Server developers have too much to do","text":"

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:

  1. A unique identifer for each operation that a set of servers supports identically
  2. A mechanism for a server to report the operations it supports
"},{"location":"fep/9fde/#a-unique-identifier-for-each-operation","title":"A unique identifier for each operation","text":"

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:

[!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).

"},{"location":"fep/9fde/#each-operation-has-one-or-more-versions","title":"Each operation has one or more versions","text":"

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.

  1. Initial implementation
  2. Support for scheduled_at
  3. Support for poll

There 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.

[!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.

"},{"location":"fep/9fde/#client-discovery-of-supported-operations-and-endpoints","title":"Client discovery of supported operations and endpoints","text":"

Clients 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.

"},{"location":"fep/9fde/#to-deploy-this","title":"To deploy this...","text":""},{"location":"fep/9fde/#server-developers","title":"Server developers","text":"

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:

  1. Determine the operation's version number, following semver backwards-compatible rules
  2. Document the behaviour of the new operation / version
  3. Include the new operation / version in the server's response
"},{"location":"fep/9fde/#client-developers","title":"Client developers","text":"

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.

"},{"location":"fep/9fde/#is-there-a-proof-of-concept","title":"Is there a proof of concept?","text":"

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:

"},{"location":"fep/9fde/#not-in-scope","title":"Not in scope","text":"

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:

  1. It requires every client development team to independently maintain a mapping between server versions and capabilities
  2. It requires client updates whenever a server is released that supports a capability the client already supports on another server

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:

"},{"location":"fep/9fde/#return-an-openapi-definition-for-the-supported-api","title":"Return an OpenAPI definition for the supported API","text":"

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:

So 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:

  1. It suggests that servers might decide to implement the same operation under different URL paths. That is unnecessary extra complexity, and per the previous section, the contract between the client and the server is if the server advertises support for operation X the definition of that operation includes that URL path it is served under.
  2. It's unfriendly to client developers; a client using this information is trying to answer the question \"Does the server support operation X?\", not \"Does API endpoint /a/b/c exist?\". So the operation identifier should be the lookup key for the map, not the endpoint.
"},{"location":"fep/9fde/#open-questions","title":"Open questions","text":"

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.

"},{"location":"fep/9fde/#what-happens-if-an-operations-owner-rebrands-or-disappears","title":"What happens if an operations \"owner\" rebrands or disappears?","text":"

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:

"},{"location":"fep/9fde/#references","title":"References","text":""},{"location":"fep/9fde/#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/a070/","title":"FEP-a070: Ordered properties for plain JSON consumers","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository a a@trwnh.com WITHDRAWN 2023-06-13 2026-04-10 #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.

"},{"location":"fep/a070/#prior-art-in-items-vs-ordereditems","title":"Prior art in 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.

"},{"location":"fep/a070/#an-exploration-of-properties-that-producers-may-wish-to-explicitly-order","title":"An exploration of properties that producers may wish to explicitly order","text":"

(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":""},{"location":"fep/a070/#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/a1d1/","title":"FEP-a1d1: ActivityPub Patch","text":"Authors Status Type Date received Tracking issue Discussions Repository Steve Bate svc-fep@stevebate.net DRAFT implementation 2026-03-19 #796 Discussions codeberg"},{"location":"fep/a1d1/#summary","title":"Summary","text":"

ActivityPub Client-to-Server (C2S) Partial Update relies on shallow top-level replacement and uses JSON null to signal property removal, but in compacted JSON-LD null is elided, so deletions can be lost in transit. This FEP introduces a Patch activity (based on JSON Patch) that cleanly separates patch operations from the target object and supports nested property updates via JSON Pointer (RFC6901).

"},{"location":"fep/a1d1/#motivation","title":"Motivation","text":"

ActivityPub client-to-server updates are currently defined as shallow partial replacements of top-level properties, with a special rule that a property set to JSON null is removed from the stored object. In practice, that removal rule is not compatible with ActivityPub's JSON-LD serialization model.

In compacted JSON-LD, null values are removed and treated as if the property wasn't specified, so a server may never receive the signal that a property is meant to be deleted. This issue has been recorded in the ActivityPub errata, and related discussion in the W3C ActivityPub issue tracker (Issues #396 and #477).

The semantics of the C2S Partial Update is also incorrect. The update is specified in the object property with an id. However, the id is the URI of the target object rather than the update object. In other words, the id is not being used to specify the identity of the Update object, but rather a completely different target object. The Patch operation separates the patch operations from the target object that will be patched.

The C2S Partial Update can only modify top-level properties. The Patch activity can modify nested properties (using JSON Pointer RFC6901 ).

Ideally, the Patch activity will replace partial Update activities in the C2S specification. For complete object replacement, the C2S Update activity can continue to be used in a consistent manner as for the Server-to-Server (S2S) Update.

"},{"location":"fep/a1d1/#patch","title":"Patch","text":"

Based on JSON Patch, a Patch object refers to a PatchOperations object. This object has an operations property containing an ordered list of operations.

Example

{\n    \"@context\": [\n        \"https://w3id.org/fep/a1d1\",\n        \"https://www.w3.org/ns/activitystreams\"\n    ],\n    \"id\": \"https://server.example/patch/1\",\n    \"type\": \"Patch\",\n    \"object\": {\n        \"type\": \"PatchOperations\",\n        \"operations\": [\n            {\n                \"op\": \"add\",\n                \"path\": \"/name\",\n                \"value\": \"Daily post\"\n            },\n            {\n                \"op\": \"remove\",\n                \"path\": \"/summary\",\n            },\n            {\n                \"value\": \"Some new content...\",\n                \"path\": \"/content\",\n                \"op\": \"replace\"\n            },\n            {\n                \"value\": \"Du nouveau contenu...\",\n                \"path\": \"/contentMap/fr\",\n                \"op\": \"replace\"\n            },\n        ]\n    },\n    \"target\": \"http://object-to-update\"\n}\n

The operations are standard JSON Patch operations so they will not be covered in detail in this FEP. The operations include:

RFC6902 contains numerous examples that describe the semantics of these operations. This FEP defines a JSON-LD context for the Patch activity and the JSON Patch language.

"},{"location":"fep/a1d1/#side-effects","title":"Side-Effects","text":"

When a Patch activity is posted to the outbox, the server MUST apply authorized operations to the target object. After the update is complete, the server SHOULD send an Update notification (with the full, updated object) notification, on behalf of the Patch actor, to any specified recipients.

"},{"location":"fep/a1d1/#json-ld-context","title":"JSON-LD Context","text":"
{\n    \"@context\": [\n        {\n            \"@version\": 1.1,\n            \"fep-a1d1\": \"https://w3id.org/fep/a1d1#\",\n            \"Patch\": \"fep-a1d1:Patch\",\n            \"PatchOperations\": {\n                \"@id\": \"fep-a1d1:PatchOperations\",\n                \"@context\": {\n                    \"operations\": {\n                        \"@id\": \"fep-a1d1:operations\",\n                        \"@container\": \"@list\",\n                        \"@context\": {\n                            \"op\": \"fep-a1d1:op\",\n                            \"path\": \"fep-a1d1:path\",\n                            \"value\": { \n                                \"@id\": \"fep-a1d1:value\", \n                                \"@type\": \"@json\"\n                            },\n                            \"from\": \"fep-a1d1:from\",\n                            \"add\": \"fep-a1d1:add\",\n                            \"remove\": \"fep-a1d1:remove\",\n                            \"replace\": \"fep-a1d1:replace\",\n                            \"move\": \"fep-a1d1:move\",\n                            \"copy\": \"fep-a1d1:copy\",\n                            \"test\": \"fep-a1d1:test\"\n                        }\n                    }\n                }\n            }\n        }\n    ]\n}\n

NOTE: The fep-a1d1:value term is defined as a JSON-LD @json type. For JSON consumers, this will not make a difference but it tells JSON-LD consumers that the content is a serialized JSON string (which may itself be JSON-LD). Linked Data applications will typically need to parse this string to retrieve the original JSON content.

"},{"location":"fep/a1d1/#implementations","title":"Implementations","text":""},{"location":"fep/a1d1/#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/a427/","title":"FEP-a427: Server Domain Migration","text":"Authors Status Date received Tracking issue Discussions Repository Dmitry Skavish skavish@gmail.com DRAFT 2026-02-25 #767 Discussions codeberg"},{"location":"fep/a427/#summary","title":"Summary","text":"

This FEP defines a best-effort protocol for migrating an entire ActivityPub server from one domain to another when the operator controls both domains and can keep the old domain online.

Example scenario used throughout this document:

This proposal introduces:

This FEP is explicitly best-effort. It does not guarantee preservation of all follows across all peers.

"},{"location":"fep/a427/#scope","title":"Scope","text":"

This FEP addresses server domain renaming: one source server migrating to one destination server with a one-to-one mapping of all identifiers. It does not cover merging multiple source servers into a single destination.

"},{"location":"fep/a427/#motivation","title":"Motivation","text":""},{"location":"fep/a427/#the-core-problem","title":"The Core Problem","text":"

In practice, ActivityPub identifiers are domain-bound: https://sunset.social/users/alice

Remote servers treat that URL as Alice's identity.

If sunset.social changes its domain to dawn.network, then: https://dawn.network/users/alice is treated as a completely different actor unless peers are informed and update their local state.

Current practice requires:

For large instances, this is inefficient and unreliable.

"},{"location":"fep/a427/#why-not-just-send-old-domain-new-domain","title":"Why Not Just Send \"Old Domain \u2192 New Domain\"?","text":"

It might seem sufficient to send:

ServerMove { from: sunset.social, to: dawn.network }\n

That is insufficient for three independent reasons.

"},{"location":"fep/a427/#1-deterministic-rewriting-why-mapping-exists","title":"1. Deterministic Rewriting (Why Mapping Exists)","text":"

Remote servers need a deterministic algorithm for rewriting identifiers.

If actor paths are preserved:

https://sunset.social/users/alice\n\u2192 https://dawn.network/users/alice\n

then a rule-based mapping works: replace origin, preserve path.

But if:

then simple domain substitution fails.

Mapping exists to define precisely how old identifiers derive new identifiers.

Without mapping:

"},{"location":"fep/a427/#2-identity-hijacking-prevention-why-acceptance-exists","title":"2. Identity Hijacking Prevention (Why Acceptance Exists)","text":"

If only one signed message were required, an attacker could send: ServerMove sunset.social \u2192 evil.example

If peers trusted that blindly, they would rewrite all identifiers and effectively transfer followers to the attacker.

A signature proves who sent the message, not that they control both domains.

Therefore migration requires:

Both domains must independently assert the migration.

Only when both sides agree may peers apply changes.

"},{"location":"fep/a427/#3-durability-and-idempotency-why-manifest-exists","title":"3. Durability and Idempotency (Why Manifest Exists)","text":"

A single notification message is:

Peers require a durable object that:

That durable object is the ServerMigration manifest.

"},{"location":"fep/a427/#terminology","title":"Terminology","text":""},{"location":"fep/a427/#servermigration-manifest","title":"ServerMigration (Manifest)","text":"

A persistent ActivityStreams object describing:

It is the canonical description of the migration.

Think of it as: \"The official migration document.\"

"},{"location":"fep/a427/#servermigrationacceptance","title":"ServerMigrationAcceptance","text":"

A persistent object hosted on the destination server confirming:

Think of it as: \"We agree to receive these identities.\"

"},{"location":"fep/a427/#servermove","title":"ServerMove","text":"

A lightweight ActivityPub activity sent to peers that says:

\"Please fetch and apply this migration manifest.\"

It does not contain full migration details.

"},{"location":"fep/a427/#mapping","title":"Mapping","text":"

A deterministic rule that converts old URIs to new URIs. Given the same mapping rules and the same input URI, every implementation MUST produce the same output URI.

Three types exist:

  1. Origin-based mapping Replace origin (scheme + host + port), preserve path.

  2. Prefix-based mapping Replace URI prefixes, preserving remaining path segments.

  3. Regex-based mapping Rewrite URIs using regular expression pattern matching (RE2 semantics).

All mapping rules MUST be reversible: for every old URI that maps to a new URI, it MUST be possible to recover the original old URI from the new URI using the same mapping definition. For OriginReplace and PrefixReplace, this is satisfied by swapping from/to values. For RegexReplace, explicit reverse rules are required.

Mapping prevents guesswork and avoids publishing global user directories.

"},{"location":"fep/a427/#uri-rewriting-scope","title":"URI Rewriting Scope","text":"

Mapping rules are used to compute new URIs for actor identifiers originating from the source server. Peers establish aliases between old and new actor URIs but MUST NOT rewrite non-actor object IDs in place (see Applying the Migration Locally).

Non-actor URIs (object IDs, collection URLs, activity IDs, media URLs) are resolved via HTTP redirects served by the source domain, not by rewriting stored values.

"},{"location":"fep/a427/#conformance","title":"Conformance","text":"

The key words MUST, SHOULD, MAY, etc. are to be interpreted as described in RFC 2119.

"},{"location":"fep/a427/#uri-normalization","title":"URI Normalization","text":"

This specification uses origin (scheme + host + port) as defined in RFC 6454 to identify servers. An origin is the tuple (scheme, host, port).

When comparing or matching URIs, implementations MUST apply the following normalization:

  1. Scheme: lowercase (e.g., HTTPS \u2192 https).
  2. Host: lowercase, converted to ASCII via punycode for internationalized domain names (IDN) per RFC 5891.
  3. Port: the default port for the scheme MUST be omitted. For https, port 443 is default and MUST NOT appear explicitly. https://example.com:443/ and https://example.com/ are the same origin.
  4. Path: preserved exactly as-is. No normalization of path segments, percent-encoding, or trailing slashes (except that the empty path is equivalent to /).

Two URIs are same-origin if and only if their normalized origins are identical.

The fromOrigin and toOrigin values in OriginReplace mappings MUST be normalized origins (e.g., https://sunset.social, not https://Sunset.Social:443).

"},{"location":"fep/a427/#high-level-flow","title":"High-Level Flow","text":"
sequenceDiagram\nautonumber\nparticipant sunset as sunset.social\nparticipant sunsetActor as sunset.social server actor\nparticipant dawn as dawn.network\nparticipant dawnActor as dawn.network server actor\nparticipant Forest as forest.instance\nparticipant ForestActor as forest.instance server actor\n\nsunset->>sunsetActor: Create ServerMigration manifest (M)\ndawn->>dawnActor: Create ServerMigrationAcceptance (A)\n\nsunsetActor->>Forest: Discover server actor via WebFinger (FEP-d556)\nForest-->>sunsetActor: Returns ForestActor\n\nsunsetActor->>ForestActor: POST ServerMove {object=M}\nForestActor->>sunset: GET M\nForestActor->>dawn: GET A\nForestActor->>ForestActor: Verify proofs, actor match, cross-references\nForestActor->>ForestActor: Establish actor aliases (old \u2192 new URIs)\nForestActor->>dawn: Fetch new actor objects\nForestActor->>ForestActor: Verify alsoKnownAs linkage\nForestActor->>ForestActor: Update delivery endpoints, keys, follows\n\nloop Poll until completed or rolledBack\nForestActor->>sunset: GET M (check state)\nend
"},{"location":"fep/a427/#specification","title":"Specification","text":""},{"location":"fep/a427/#1-server-actor-discovery","title":"1. Server Actor Discovery","text":"

Server actor discovery MUST follow FEP-d556: Server Actor Discovery, which defines a \"server\" as an origin (scheme + host + port) and specifies how to discover the server-level actor via WebFinger.

The source server, destination server, and all peers MUST expose a server-level actor discoverable via FEP-d556.

Peers MUST discover and use the server actor inbox for ServerMove delivery.

"},{"location":"fep/a427/#1a-webfinger-behavior-during-migration","title":"1a. WebFinger Behavior During Migration","text":"

During an active or completed migration, the source server's WebFinger responses MUST reflect the migration state so that peers performing fresh lookups discover the canonical identities.

For actor lookups (e.g., ?resource=acct:alice@sunset.social):

During the active and completed phases, the source server MUST return a WebFinger response that includes an aliases array containing the new actor URI:

{\n  \"subject\": \"acct:alice@sunset.social\",\n  \"aliases\": [\n    \"https://sunset.social/users/alice\",\n    \"https://dawn.network/users/alice\"\n  ],\n  \"links\": [\n    {\n      \"rel\": \"self\",\n      \"type\": \"application/activity+json\",\n      \"href\": \"https://dawn.network/users/alice\"\n    }\n  ]\n}\n

The rel=\"self\" link MUST point to the new canonical actor URI on the destination server. This ensures that peers performing a fresh WebFinger lookup are directed to the new identity even if they have not yet processed the ServerMove.

For server actor lookups (e.g., ?resource=https://sunset.social/):

The source server MUST continue to return its own server actor during the active phase (the server actor is needed to verify the manifest proof). After the migration is completed, the server actor WebFinger response SHOULD include an alias pointing to the new server actor.

"},{"location":"fep/a427/#2-servermigration-object","title":"2. ServerMigration Object","text":"

A ServerMigration object MUST include:

Property Type Description @context Array MUST include \"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/a427\", and \"https://w3id.org/security/data-integrity/v1\" id URI Stable, dereferenceable URI hosted on the source server type String \"ServerMigration\" source URI Server actor ID of the source server target URI Server actor ID of the destination server mapping Object Mapping rules (see Mapping Rules) state String One of: active, completed, rolledBack published xsd:dateTime When the manifest was first published updated xsd:dateTime When the manifest state last changed (MUST be present when state is not active) acceptance URI Dereferenceable URL of the ServerMigrationAcceptance on the destination server proof Object FEP-8b32 Object Integrity Proof (see Cryptographic Proofs)"},{"location":"fep/a427/#pre-generating-ids","title":"Pre-generating IDs","text":"

Because the ServerMigration references its ServerMigrationAcceptance (via acceptance) and the acceptance references the manifest (via migration), both IDs MUST be determined before either object is published. Since both objects are controlled by the same operator, the recommended approach is:

  1. Generate both IDs deterministically (e.g., based on a shared migration identifier such as a date or UUID).
  2. Publish the ServerMigration manifest on the source server.
  3. Publish the ServerMigrationAcceptance on the destination server.

Example ID scheme:

ServerMigration:  https://sunset.social/.well-known/server-migration/2026-02-23\nAcceptance:       https://dawn.network/.well-known/server-migration-acceptance/2026-02-23\n
"},{"location":"fep/a427/#full-example","title":"Full Example","text":"
{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/a427\",\n    \"https://w3id.org/security/data-integrity/v1\"\n  ],\n  \"id\": \"https://sunset.social/.well-known/server-migration/2026-02-23\",\n  \"type\": \"ServerMigration\",\n  \"source\": \"https://sunset.social/actor\",\n  \"target\": \"https://dawn.network/actor\",\n  \"mapping\": {\n    \"type\": \"OriginReplace\",\n    \"fromOrigin\": \"https://sunset.social\",\n    \"toOrigin\": \"https://dawn.network\"\n  },\n  \"state\": \"active\",\n  \"published\": \"2026-02-23T00:00:00Z\",\n  \"acceptance\": \"https://dawn.network/.well-known/server-migration-acceptance/2026-02-23\",\n  \"proof\": {\n    \"type\": \"DataIntegrityProof\",\n    \"cryptosuite\": \"eddsa-jcs-2022\",\n    \"verificationMethod\": \"https://sunset.social/actor#ed25519-key\",\n    \"proofPurpose\": \"assertionMethod\",\n    \"proofValue\": \"z...\"\n  }\n}\n
"},{"location":"fep/a427/#3-servermigrationacceptance-object","title":"3. ServerMigrationAcceptance Object","text":"

Hosted on the destination server (dawn.network), MUST include:

Property Type Description @context Array MUST include \"https://www.w3.org/ns/activitystreams\", \"https://w3id.org/fep/a427\", and \"https://w3id.org/security/data-integrity/v1\" id URI Stable, dereferenceable URI hosted on the destination server type String \"ServerMigrationAcceptance\" migration URI Reference to the ServerMigration manifest source URI Server actor ID of the source server (MUST match manifest source) target URI Server actor ID of the destination server (MUST match manifest target) proof Object FEP-8b32 Object Integrity Proof signed by the destination server actor"},{"location":"fep/a427/#full-example_1","title":"Full Example","text":"
{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/a427\",\n    \"https://w3id.org/security/data-integrity/v1\"\n  ],\n  \"id\": \"https://dawn.network/.well-known/server-migration-acceptance/2026-02-23\",\n  \"type\": \"ServerMigrationAcceptance\",\n  \"migration\": \"https://sunset.social/.well-known/server-migration/2026-02-23\",\n  \"source\": \"https://sunset.social/actor\",\n  \"target\": \"https://dawn.network/actor\",\n  \"proof\": {\n    \"type\": \"DataIntegrityProof\",\n    \"cryptosuite\": \"eddsa-jcs-2022\",\n    \"verificationMethod\": \"https://dawn.network/actor#ed25519-key\",\n    \"proofPurpose\": \"assertionMethod\",\n    \"proofValue\": \"z...\"\n  }\n}\n
"},{"location":"fep/a427/#4-servermove-activity","title":"4. ServerMove Activity","text":"

A lightweight activity delivered to peers to trigger migration processing.

Property Type Description @context Array MUST include \"https://www.w3.org/ns/activitystreams\" and \"https://w3id.org/fep/a427\" type String \"ServerMove\" actor URI Server actor of the source server object URI URL of the ServerMigration manifest

Example:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/a427\"\n  ],\n  \"type\": \"ServerMove\",\n  \"actor\": \"https://sunset.social/actor\",\n  \"object\": \"https://sunset.social/.well-known/server-migration/2026-02-23\"\n}\n

This MUST be delivered via signed HTTP POST to the peer's server actor inbox.

"},{"location":"fep/a427/#5-cryptographic-proofs","title":"5. Cryptographic Proofs","text":"

Both ServerMigration and ServerMigrationAcceptance objects MUST include a proof property conforming to FEP-8b32: Object Integrity Proofs.

FEP-8b32 proofs are based on the W3C Data Integrity specification and attach a cryptographic signature directly to the object. This ensures authenticity can be verified regardless of how the object was obtained (direct fetch, cache, forwarding).

"},{"location":"fep/a427/#requirements","title":"Requirements","text":""},{"location":"fep/a427/#verification-method-resolution","title":"Verification Method Resolution","text":"

Per FEP-8b32, the verificationMethod in each proof references a key in a controlled identifier document (typically the actor object). Peers MUST:

  1. Fetch the server actor object identified by source (or target for the acceptance).
  2. Verify that the verificationMethod URI resolves to a key listed in the actor's assertionMethod property (see also FEP-521a).
  3. Verify that the verificationMethod is same-origin with the signed document. Cross-origin verification methods MUST be rejected unless the implementation explicitly supports decentralized identifier (DID) based methods.
"},{"location":"fep/a427/#proof-stability","title":"Proof Stability","text":"

The proof property covers the object at a point in time. When the state field changes (e.g., from active to completed), the source server MUST re-sign the manifest with an updated proof. Peers MUST re-verify the proof each time they fetch the manifest during polling.

The ServerMove activity itself is authenticated via HTTP Signatures on the POST request, as is standard for ActivityPub server-to-server delivery.

"},{"location":"fep/a427/#6-fetch-safety","title":"6. Fetch Safety","text":"

Processing a ServerMove requires fetching URLs supplied by a remote party. Without constraints, this creates SSRF (Server-Side Request Forgery) and resource-exhaustion vectors. Implementations MUST enforce the following:

"},{"location":"fep/a427/#origin-constraints","title":"Origin Constraints","text":""},{"location":"fep/a427/#resource-limits","title":"Resource Limits","text":""},{"location":"fep/a427/#receiving-rules","title":"Receiving Rules","text":"

Upon receiving ServerMove, a peer:

  1. MUST verify the HTTP Signature on the delivery request.
  2. MUST fetch the ServerMigration manifest from the URL in object.
  3. MUST verify that ServerMove.actor equals ServerMigration.source. If they differ, the peer MUST reject the migration.
  4. MUST verify the manifest's FEP-8b32 proof by resolving the verificationMethod from the source server actor's assertionMethod (see Cryptographic Proofs).
  5. MUST fetch the ServerMigrationAcceptance from the URL in acceptance.
  6. MUST verify the acceptance's FEP-8b32 proof by resolving the verificationMethod from the destination server actor's assertionMethod.
  7. MUST verify that acceptance.migration references the manifest's id.
  8. MUST verify that source and target are consistent across both objects.
  9. MUST apply the migration locally only if all verification succeeds (see Applying the Migration Locally).
  10. MUST begin polling the manifest for lifecycle state changes (see Polling).
"},{"location":"fep/a427/#mapping-resolution","title":"Mapping Resolution","text":"

Mapping rules define how to derive a new URI from an old URI. Mapping is applied only to actor URIs (see Applying the Migration Locally).

flowchart TD\nA[Old actor URI<br/>e.g. https://sunset.social/users/alice]\n  --> B{Does URI origin match<br/>source origin?}\n\nB -->|No| G[Leave unchanged]\nB -->|Yes| C{Mapping type?}\n\nC -->|OriginReplace| D[Replace origin<br/>https://sunset.social \u2192 https://dawn.network]\nD --> E[New URI<br/>https://dawn.network/users/alice]\n\nC -->|PrefixReplace| F[Try rules in order<br/>first match wins]\nF -->|Match found| E\nF -->|No match| H[Leave unchanged<br/>Log warning]\n\nC -->|RegexReplace| I[Try regex rules in order<br/>first match wins]\nI -->|Match found| J{Result is valid<br/>HTTPS URI on target?}\nJ -->|Yes| E\nJ -->|No| H\nI -->|No match| H

When a PrefixReplace or RegexReplace mapping contains multiple rules, peers MUST evaluate them in the order they appear in the rules array. The first matching rule wins. If no rule matches a URI whose origin matches the source server, the URI is left unchanged and the peer SHOULD log a warning for operator review.

Peers MUST treat migration as idempotent per manifest id.

"},{"location":"fep/a427/#applying-the-migration-locally","title":"Applying the Migration Locally","text":"

Peers MUST NOT interpret migration as a naive string-replace across all stored data. A peer's local state includes delivery endpoints, public keys, follow relationships with internal identifiers, moderation decisions, cached objects with provenance information, and historical activities that reference old actor IDs. Blindly rewriting all occurrences would break signature verification on historical objects, corrupt Undo/Accept semantics (which reference the ID at the time the original activity was created), and bypass blocklists and moderation state.

Instead, peers MUST apply the following model.

"},{"location":"fep/a427/#step-1-establish-actor-aliases","title":"Step 1: Establish Actor Aliases","text":"

For each actor whose URI origin matches the source server, compute the new URI using the mapping rules. Register the old URI as an alias of the new URI:

"},{"location":"fep/a427/#step-2-fetch-and-verify-new-actors","title":"Step 2: Fetch and Verify New Actors","text":"

For each mapped actor that the peer has a local relationship with (follows, followers, cached profiles), the peer MUST:

  1. Fetch the new actor object from the new URI.
  2. Verify linkage: the new actor SHOULD include an alsoKnownAs property (as an array) containing the old actor URI. If present, this confirms the migration from the actor's perspective.
  3. Update locally-stored delivery metadata: inbox, sharedInbox, endpoints, and publicKey MUST be replaced with the values from the fetched new actor object.

If the new actor cannot be fetched (network error, 404), the peer SHOULD retain the alias but MUST NOT update delivery metadata. The peer SHOULD retry on subsequent delivery attempts.

"},{"location":"fep/a427/#step-3-carry-over-moderation-state","title":"Step 3: Carry Over Moderation State","text":"

Block lists, mutes, silences, and other moderation decisions associated with the old actor URI MUST be carried over to the new canonical URI. The old alias MUST also continue to match moderation rules so that references in historical content remain covered.

"},{"location":"fep/a427/#step-4-non-actor-uris-objects-collections-media","title":"Step 4: Non-Actor URIs (Objects, Collections, Media)","text":"

Peers MUST NOT rewrite non-actor object IDs (notes, articles, activity IDs, collection URLs, media URLs) in place. Object IDs are treated as identifiers in the ActivityPub data model, not merely as addresses. Rewriting them in place would create:

Instead, non-actor URIs from the source server continue to be stored under their original IDs. When the old domain serves HTTP 301 redirects (see Old Domain Lifetime), peers that need to re-fetch these objects will be transparently redirected to the new location.

Peers MAY maintain an internal equivalence index (old URI \u2192 new URI) for non-actor URIs to optimize lookups, but the stored canonical ID MUST remain the original.

"},{"location":"fep/a427/#step-5-transfer-follow-relationships","title":"Step 5: Transfer Follow Relationships","text":"

All existing follow relationships involving actors from the source server MUST be automatically transferred to the new canonical URIs:

This is an automatic, server-side operation. Individual follow approval policies on the receiving server are not re-triggered \u2014 the follow relationship is preserved, not re-established.

"},{"location":"fep/a427/#source-server-requirements","title":"Source Server Requirements","text":"

To support the fetch-and-verify step, the source server (or destination server, since they are operated by the same party) SHOULD ensure that each migrated actor on the new domain includes an alsoKnownAs property referencing the old URI as an array:

{\n  \"id\": \"https://dawn.network/users/alice\",\n  \"type\": \"Person\",\n  \"alsoKnownAs\": [\"https://sunset.social/users/alice\"],\n  \"inbox\": \"https://dawn.network/users/alice/inbox\",\n  \"publicKey\": { \"...\" }\n}\n

This provides per-actor verification that complements the server-level manifest proofs.

"},{"location":"fep/a427/#actor-opt-out","title":"Actor Opt-Out","text":"

Individual actors cannot opt out of a server migration. Domain migration is an operational decision made by the server operator and applies to all actors hosted on that server. This is analogous to other server-wide operational changes (TLS certificate rotation, IP address changes) that do not require per-actor consent.

Actors who disagree with the migration may independently move to a different server using per-actor Move after the migration completes.

"},{"location":"fep/a427/#polling","title":"Polling","text":"

After applying a migration, peers MUST periodically poll the ServerMigration manifest to track lifecycle state changes.

"},{"location":"fep/a427/#polling-strategy","title":"Polling Strategy","text":"

Peers SHOULD use the following polling schedule:

Time Since Migration Applied Poll Interval First 24 hours Every 1 hour Days 2\u20137 Every 6 hours Days 8\u201330 Every 24 hours After 30 days Every 7 days

Peers MUST continue polling until the manifest state changes to completed or rolledBack.

"},{"location":"fep/a427/#on-state-change-to-completed","title":"On State Change to completed","text":"

The peer marks the migration as finalized. No further polling is needed for this manifest.

"},{"location":"fep/a427/#on-state-change-to-rolledback","title":"On State Change to rolledBack","text":"

The peer MUST reverse the migration by restoring old actor aliases as canonical and demoting the new URIs. Since actors were migrated using the alias model (Step 1), the old URI is still stored. No mapping rule inversion is needed for actor URIs.

The peer MUST re-fetch actor objects from the original (source) server to restore delivery metadata (inbox, keys).

Since non-actor URIs were never rewritten (Step 4), no reversal is needed for object IDs, collection URLs, or media URLs.

However, if an individual actor performed a per-actor Move activity during the active phase (i.e., the actor independently moved to a different server while the server-wide migration was in progress), that per-actor Move takes precedence. The server-wide rollback MUST NOT override an actor's intentional individual move. Peers SHOULD check whether each actor has a more recent per-actor Move on record before reversing the alias.

After processing a rollback, the peer MUST mark this migration ID as permanently invalid. If a ServerMove referencing this same manifest id is received again in the future, the peer MUST reject it.

"},{"location":"fep/a427/#on-fetch-failure","title":"On Fetch Failure","text":"

If a poll request fails (network error, HTTP 5xx), the peer SHOULD retry using exponential backoff starting at 1 hour, capped at 24 hours.

If the manifest becomes permanently unreachable (HTTP 404 or 410 for 7 consecutive days), the peer SHOULD stop polling. However, the peer MUST NOT infer completed from unreachability alone. The migration remains in whatever state was last observed and verified. The peer SHOULD log a warning so operators can investigate.

"},{"location":"fep/a427/#lifecycle","title":"Lifecycle","text":"
stateDiagram-v2\n[*] --> Active\nActive --> Completed\nActive --> RolledBack

RolledBack and Completed are terminal states. A rolled-back migration cannot be reactivated. If the operator wishes to attempt the migration again, a new ServerMigration manifest with a new id MUST be created.

"},{"location":"fep/a427/#grace-period","title":"Grace Period","text":"

Once a migration reaches the completed state, it MUST NOT be changed to rolledBack. Rollback is only available from the active state. This prevents long-term state instability where peers that already finalized the migration would need to reverse months-old changes.

Operators who need to reverse a completed migration MUST treat it as a new migration in the opposite direction (from dawn.network back to sunset.social), using a new manifest.

"},{"location":"fep/a427/#per-actor-move-precedence","title":"Per-Actor Move Precedence","text":"

Per-actor Move activities always take precedence over server-wide migration state. If an individual actor performed a Move to a third-party server while the server-wide migration was active, that actor's individual move MUST NOT be overridden by a subsequent server-wide rollback. Peers SHOULD track per-actor moves independently of server-wide migrations.

Peers MUST:

"},{"location":"fep/a427/#migration-history","title":"Migration History","text":"

Servers MUST retain all ServerMigration manifests indefinitely, regardless of their terminal state. This allows peers that were offline during the migration window to discover and process migrations after the fact.

Both the source and destination servers SHOULD serve the manifest at its original URL. The destination server SHOULD additionally serve a copy of the manifest so that peers can discover it even if the source domain eventually becomes unavailable.

"},{"location":"fep/a427/#conflicting-migrations","title":"Conflicting Migrations","text":"

A source server MUST NOT have more than one migration in the active state at any time. If a peer receives a ServerMove for a source server that already has an active migration with a different manifest id, the peer MUST reject the new migration and SHOULD log a warning.

Once a migration reaches a terminal state (completed or rolledBack), a new migration from the same source server is permitted.

"},{"location":"fep/a427/#chained-migrations","title":"Chained Migrations","text":"

If a server migrates from A to B (completed), and later from B to C, peers that processed the first migration will encounter the second migration naturally. Peers that missed the first migration and later discover A\u2192B (completed) and B\u2192C (active or completed) SHOULD apply both in sequence.

"},{"location":"fep/a427/#security-considerations","title":"Security Considerations","text":"

Migration requires two-party agreement.

sequenceDiagram\nparticipant Evil as evil.example\nparticipant Forest as forest.instance\n\nEvil->>Forest: ServerMove sunset.social->evil.example\nForest->>sunset.social: GET Manifest\nForest->>Forest: Verify proof \u2014 signed by sunset.social? No.\nForest->>Forest: Reject migration

A migration is valid only if:

Without both proofs, identity hijacking is prevented.

"},{"location":"fep/a427/#ssrf-and-resource-exhaustion","title":"SSRF and Resource Exhaustion","text":"

Because peers fetch URLs supplied by remote parties, implementations MUST enforce the origin constraints and resource limits defined in Fetch Safety. In particular:

"},{"location":"fep/a427/#domain-takeover-after-migration","title":"Domain Takeover After Migration","text":"

If the source domain (sunset.social) expires after migration and is re-registered by a different party, the new registrant could stand up a server actor with new keys and attempt to publish a fraudulent ServerMigration manifest.

To mitigate this risk:

"},{"location":"fep/a427/#privacy-considerations","title":"Privacy Considerations","text":""},{"location":"fep/a427/#fallback","title":"Fallback","text":"

If forest.instance does not support this FEP:

"},{"location":"fep/a427/#old-domain-lifetime","title":"Old Domain Lifetime","text":"

This FEP does not mandate a specific duration for maintaining the old domain. However, the following recommendations apply:

Operators should be aware that some peers may be offline for extended periods. The longer the old domain remains operational, the more peers will successfully process the migration.

"},{"location":"fep/a427/#old-uri-resolution-tombstones-vs-redirects","title":"Old URI Resolution (Tombstones vs Redirects)","text":"

When the old domain is still operational, old URIs SHOULD be handled as HTTP 301 (Moved Permanently) redirects to the corresponding new URIs. This is preferred over returning Tombstone objects because:

Tombstone objects SHOULD NOT be used for migrated resources because they signal deletion, which may cause peers to remove cached content rather than update references.

The source server SHOULD return the manifest itself (not a redirect) at its .well-known URL, so that peers can always fetch it for verification.

"},{"location":"fep/a427/#deployment-phases","title":"Deployment Phases","text":"
gantt\ntitle sunset.social \u2192 dawn.network Migration\ndateFormat  YYYY-MM-DD\n\nsection Prepare\nStand up dawn.network :a1, 2026-02-23, 3d\nGenerate manifest and acceptance IDs :a1b, after a1, 1d\nPublish manifests on both servers     :a2, after a1b, 1d\n\nsection Notify\nDiscover peers           :b1, after a2, 3d\nSend ServerMove waves    :b2, after b1, 7d\n\nsection Fallback\nPer-actor Move           :c1, after b1, 14d\nMaintain old domain      :c2, 2026-02-23, 90d\n\nsection Complete\nMark completed           :d1, after b2, 1d
"},{"location":"fep/a427/#mapping-rules","title":"Mapping Rules","text":"

Scenario used in examples:

"},{"location":"fep/a427/#1-simple-origin-based-mapping","title":"1. Simple Origin-Based Mapping","text":""},{"location":"fep/a427/#use-case","title":"Use Case","text":"

URL structure remains identical. Only the origin (scheme + host) changes.

"},{"location":"fep/a427/#rule-definition-conceptual","title":"Rule Definition (Conceptual)","text":"
If URI origin equals \"https://sunset.social\"\nReplace origin with \"https://dawn.network\"\nPreserve path\n
"},{"location":"fep/a427/#example-transformations","title":"Example Transformations","text":"
https://sunset.social/users/alice\n\u2192 https://dawn.network/users/alice\n\nhttps://sunset.social/@bob\n\u2192 https://dawn.network/@bob\n\nhttps://sunset.social/groups/astronomy\n\u2192 https://dawn.network/groups/astronomy\n\nhttps://sunset.social/notes/12345\n\u2192 https://dawn.network/notes/12345\n
"},{"location":"fep/a427/#manifest-example","title":"Manifest Example","text":"
{\n  \"type\": \"ServerMigration\",\n  \"mapping\": {\n    \"type\": \"OriginReplace\",\n    \"fromOrigin\": \"https://sunset.social\",\n    \"toOrigin\": \"https://dawn.network\"\n  }\n}\n
"},{"location":"fep/a427/#2-prefix-based-mapping-path-changes","title":"2. Prefix-Based Mapping (Path Changes)","text":""},{"location":"fep/a427/#use-case_1","title":"Use Case","text":"

Old server used /users/ New server uses /profile/

"},{"location":"fep/a427/#transformations","title":"Transformations","text":"
https://sunset.social/users/alice\n\u2192 https://dawn.network/profile/alice\n
"},{"location":"fep/a427/#rule-definition","title":"Rule Definition","text":"
Match prefix: https://sunset.social/users/\nRewrite to: https://dawn.network/profile/\nAppend remaining path segment\n
"},{"location":"fep/a427/#manifest-example_1","title":"Manifest Example","text":"
{\n  \"mapping\": {\n    \"type\": \"PrefixReplace\",\n    \"rules\": [\n      {\n        \"fromPrefix\": \"https://sunset.social/users/\",\n        \"toPrefix\": \"https://dawn.network/profile/\"\n      }\n    ]\n  }\n}\n

Deterministic. Still does not expose actor lists.

"},{"location":"fep/a427/#3-multiple-actor-types-users-groups","title":"3. Multiple Actor Types (Users + Groups)","text":""},{"location":"fep/a427/#use-case_2","title":"Use Case","text":"

Users and groups have different routes.

https://sunset.social/users/alice\n\u2192 https://dawn.network/u/alice\n\nhttps://sunset.social/groups/astronomy\n\u2192 https://dawn.network/g/astronomy\n
"},{"location":"fep/a427/#manifest-example_2","title":"Manifest Example","text":"
{\n  \"mapping\": {\n    \"type\": \"PrefixReplace\",\n    \"rules\": [\n      {\n        \"fromPrefix\": \"https://sunset.social/users/\",\n        \"toPrefix\": \"https://dawn.network/u/\"\n      },\n      {\n        \"fromPrefix\": \"https://sunset.social/groups/\",\n        \"toPrefix\": \"https://dawn.network/g/\"\n      }\n    ]\n  }\n}\n

Rules are evaluated in order. The first matching rule is applied.

"},{"location":"fep/a427/#4-catch-all-with-prefix-rules","title":"4. Catch-All with Prefix Rules","text":"

When using PrefixReplace, it is RECOMMENDED to include a catch-all rule as the last entry to handle URIs that don't match specific prefix patterns:

{\n  \"mapping\": {\n    \"type\": \"PrefixReplace\",\n    \"rules\": [\n      {\n        \"fromPrefix\": \"https://sunset.social/users/\",\n        \"toPrefix\": \"https://dawn.network/u/\"\n      },\n      {\n        \"fromPrefix\": \"https://sunset.social/groups/\",\n        \"toPrefix\": \"https://dawn.network/g/\"\n      },\n      {\n        \"fromPrefix\": \"https://sunset.social/\",\n        \"toPrefix\": \"https://dawn.network/\"\n      }\n    ]\n  }\n}\n
"},{"location":"fep/a427/#5-regex-based-mapping","title":"5. Regex-Based Mapping","text":""},{"location":"fep/a427/#use-case_3","title":"Use Case","text":"

URL structure changed in ways that cannot be expressed with simple prefix substitution:

https://sunset.social/@alice\n\u2192 https://dawn.network/users/alice\n
"},{"location":"fep/a427/#manifest-example_3","title":"Manifest Example","text":"
{\n  \"mapping\": {\n    \"type\": \"RegexReplace\",\n    \"rules\": [\n      {\n        \"pattern\": \"^https://sunset\\\\.social/@([^/]+)$\",\n        \"replacement\": \"https://dawn.network/users/$1\"\n      },\n      {\n        \"pattern\": \"^https://sunset\\\\.social/(.*)$\",\n        \"replacement\": \"https://dawn.network/$1\"\n      }\n    ]\n  }\n}\n

Rules are evaluated in order. The first matching rule is applied.

"},{"location":"fep/a427/#safety-requirements","title":"Safety Requirements","text":"

Regex-based mapping is powerful but carries implementation risks. Peers processing RegexReplace mappings:

Implementations that cannot guarantee linear-time regex matching SHOULD reject RegexReplace mappings and treat the ServerMove as unsupported, falling back to per-actor Move processing.

"},{"location":"fep/a427/#vocabulary","title":"Vocabulary","text":"

This FEP defines the following terms under the namespace https://w3id.org/fep/a427#:

"},{"location":"fep/a427/#types","title":"Types","text":"Term URI Description ServerMigration https://w3id.org/fep/a427#ServerMigration A durable manifest describing a server domain migration ServerMigrationAcceptance https://w3id.org/fep/a427#ServerMigrationAcceptance Confirmation from the destination server accepting the migration ServerMove https://w3id.org/fep/a427#ServerMove Activity notifying peers to process a migration OriginReplace https://w3id.org/fep/a427#OriginReplace Mapping type: replace URI origin, preserve path PrefixReplace https://w3id.org/fep/a427#PrefixReplace Mapping type: replace URI prefixes RegexReplace https://w3id.org/fep/a427#RegexReplace Mapping type: regex-based URI rewriting (RE2 semantics required)"},{"location":"fep/a427/#properties","title":"Properties","text":"Term URI Domain Range Description source https://w3id.org/fep/a427#source ServerMigration, ServerMigrationAcceptance URI Server actor of the source server target https://w3id.org/fep/a427#target ServerMigration, ServerMigrationAcceptance URI Server actor of the destination server mapping https://w3id.org/fep/a427#mapping ServerMigration Object Mapping rules for URI rewriting state https://w3id.org/fep/a427#state ServerMigration String Lifecycle state: active, completed, rolledBack acceptance https://w3id.org/fep/a427#acceptance ServerMigration URI Reference to the ServerMigrationAcceptance migration https://w3id.org/fep/a427#migration ServerMigrationAcceptance URI Reference to the ServerMigration manifest fromOrigin https://w3id.org/fep/a427#fromOrigin OriginReplace String Normalized source origin (scheme + host + port) toOrigin https://w3id.org/fep/a427#toOrigin OriginReplace String Normalized destination origin rules https://w3id.org/fep/a427#rules PrefixReplace, RegexReplace Array Ordered list of replacement rules fromPrefix https://w3id.org/fep/a427#fromPrefix Prefix rule object String URI prefix to match toPrefix https://w3id.org/fep/a427#toPrefix Prefix rule object String Replacement URI prefix pattern https://w3id.org/fep/a427#pattern Regex rule object String RE2-compatible regex pattern replacement https://w3id.org/fep/a427#replacement Regex rule object String Replacement string with capture group references

Note: This specification uses published and updated from the ActivityStreams 2.0 vocabulary (https://www.w3.org/ns/activitystreams#published, https://www.w3.org/ns/activitystreams#updated) rather than defining custom date-time properties.

"},{"location":"fep/a427/#json-ld-context","title":"JSON-LD Context","text":"

The associated context document at https://w3id.org/fep/a427 defines the term mappings:

{\n  \"@context\": {\n    \"fep-a427\": \"https://w3id.org/fep/a427#\",\n    \"ServerMigration\": \"fep-a427:ServerMigration\",\n    \"ServerMigrationAcceptance\": \"fep-a427:ServerMigrationAcceptance\",\n    \"ServerMove\": \"fep-a427:ServerMove\",\n    \"OriginReplace\": \"fep-a427:OriginReplace\",\n    \"PrefixReplace\": \"fep-a427:PrefixReplace\",\n    \"RegexReplace\": \"fep-a427:RegexReplace\",\n    \"source\": { \"@id\": \"fep-a427:source\", \"@type\": \"@id\" },\n    \"target\": { \"@id\": \"fep-a427:target\", \"@type\": \"@id\" },\n    \"mapping\": \"fep-a427:mapping\",\n    \"state\": \"fep-a427:state\",\n    \"acceptance\": { \"@id\": \"fep-a427:acceptance\", \"@type\": \"@id\" },\n    \"migration\": { \"@id\": \"fep-a427:migration\", \"@type\": \"@id\" },\n    \"fromOrigin\": \"fep-a427:fromOrigin\",\n    \"toOrigin\": \"fep-a427:toOrigin\",\n    \"rules\": { \"@id\": \"fep-a427:rules\", \"@container\": \"@list\" },\n    \"fromPrefix\": \"fep-a427:fromPrefix\",\n    \"toPrefix\": \"fep-a427:toPrefix\",\n    \"pattern\": \"fep-a427:pattern\",\n    \"replacement\": \"fep-a427:replacement\"\n  }\n}\n
"},{"location":"fep/a427/#relationship-to-existing-work","title":"Relationship to Existing Work","text":"

This FEP:

"},{"location":"fep/a427/#final-notes","title":"Final Notes","text":"

Server-wide domain migration is inherently messy because identifiers are authority-bound.

This FEP does not attempt to make migration perfect.

It attempts to make it:

"},{"location":"fep/a427/#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/a4ed/","title":"FEP-a4ed: The Fediverse Enhancement Proposal Process","text":"Authors Status Date received Date final Tracking issue Discussions Repository pukkamustard pukkamustard@posteo.net FINAL 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:

"},{"location":"fep/a4ed/#required-sections","title":"Required Sections","text":"

Every FEP should include at least the following sections:

Following sections may be included in a proposal:

"},{"location":"fep/a4ed/#copyright","title":"Copyright","text":"

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 --> [*]
  1. A Fediverse Enhancement Proposal can be submitted by individuals or groups of individuals (authors). See the SUBMISSION.md file for a list of accepted submission methods.
  2. Within seven days one of the facilitators will read and respond to the proposal. The facilitator checks if the proposal conforms to the required structure and fits the scope and objective of the FEPs. The facilitator may request the authors to clarify, justify, or withdraw the proposal. Such a request must not reflect the personal bias of a facilitator. Rather, it will be made strictly to maintain a high quality of submissions. The facilitators reserve the right to reject a submission when a proposal amounts to blatant misuse of the process. The authors may seek feedback from the wider community if the submitted proposal is rejected or clarifications are requested.
  3. If a FEP facilitator approves a submission it receives the status 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.
  4. Registered proposals must have the following metadata attributes: slug, authors, status, dateReceived, trackingIssue and discussionsTo.
  5. When the status is changed to FINAL or WITHDRAWN, facilitator adds dateFinalized or dateWithdrawn attributes to the proposal metadata.
  6. While a proposal has the status DRAFT:
  7. Authors are responsible for initiating community discussion and collecting feedback.
  8. Authors may submit updates to the proposal which will be checked in to the repository by a facilitator. Others may also submit updates, but in this case a change must be approved by at least one author.
  9. Authors may withdraw the submission upon which a facilitator will set the status of the submission to WITHDRAWN.
  10. After at least 60 days the authors may request the proposal to be finalized. This is done by requesting final comments on the proposal.
  11. If there are no community objections within 14 days and the authors can show that they have initiated sufficient awareness and discussion of the proposal, a facilitator will set the status of the submission to FINAL.
  12. If authors have not requested the proposal to be finalized, and there were no updates for 2 years or longer, a facilitator will set the status of the submission to WITHDRAWN.
  13. A proposal with status FINAL can not be changed or updated in a way that would lead to adjustments to implementations. Minor corrections are allowed.
  14. Any substantial change to finalized proposal must be submitted as a separate FEP.

  15. The new FEP MUST include a replaces metadata attribute pointing to FEPs it supersedes.

  16. If the new FEP becomes FINAL, the original one MUST point to it in an added replacedBy metadata attribute.

  17. A proposal with status WITHDRAWN remains in the repository and can be resubmitted.

  18. After a proposal becomes FINAL a facilitator will archive all discussions linked in the tracking issue and add the resulting archive links to the tracking issue.
"},{"location":"fep/a4ed/#facilitators","title":"Facilitators","text":"

A list of facilitators is maintained in the FACILITATORS.md file at the root of the FEP repository.

"},{"location":"fep/a4ed/#submission-methods","title":"Submission Methods","text":"

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.

"},{"location":"fep/a4ed/#fep-process-governance","title":"FEP Process Governance","text":"

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":""},{"location":"fep/a4ed/#copyright_1","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/a5c5/","title":"FEP-a5c5: Web Syndication Methods","text":"Authors Status Date received Tracking issue Discussions Repository AvidSeeker avidseeker7@protonmail.com DRAFT 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.

"},{"location":"fep/a5c5/#syndication-methods","title":"Syndication Methods","text":""},{"location":"fep/a5c5/#creating-feeds","title":"Creating Feeds","text":"

To create a syndication feed, servers must append .rss or .atom to the URL of an object. For example:

"},{"location":"fep/a5c5/#mirrored-profiles","title":"Mirrored Profiles","text":"

For 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:

"},{"location":"fep/a5c5/#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/a974/","title":"FEP-a974: All Actor types should be followable","text":"Authors Status Date received Tracking issue Discussions Repository James Smith james@floppy.org.uk DRAFT 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.

"},{"location":"fep/a974/#motivation","title":"Motivation","text":"

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:

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.

"},{"location":"fep/a974/#impact","title":"Impact","text":"

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.

"},{"location":"fep/a974/#references","title":"References","text":""},{"location":"fep/a974/#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/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.net FINAL 2024-10-19 2025-03-14 #424 Discussions codeberg"},{"location":"fep/ae0c/#summary","title":"Summary","text":"

Relays are important components in 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 wider federated social network, both as consumers and producers of Fediverse content.

Several styles of relays exist in the Activity Fediverse. This FEP describes 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 ActivityPub Follow 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 a 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.

"},{"location":"fep/ae0c/#receiving-messages-from-relays","title":"Receiving Messages from Relays","text":"

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.

"},{"location":"fep/ae0c/#undofollow","title":"Undo/Follow","text":"

Verify that the actor is a known relay client and, if so, remove the client actor from the set of relay actor followers.

"},{"location":"fep/ae0c/#activity-relaying","title":"Activity Relaying","text":"

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.

"},{"location":"fep/ae0c/#mastodon-ld-signatures","title":"Mastodon LD Signatures","text":"

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 and that string is then signed.

"},{"location":"fep/ae0c/#signing-a-json-ld-activity","title":"Signing a JSON-LD Activity","text":"
  1. Create a signature document with only the 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.)
  2. Create a canonical RDF representation of the signature document. This requires JSON-LD expansion using standard algorithms (JSON-LD-ALGO) and conversion to RDF using the Universal RDF Dataset Canonicalization Algorithm 2015 (RDF-CANON). The serialized RDF is then hashed using SHA256 and a hexdigest is created.
  3. Create a SHA256 hex digest for the activity document (without the signature document) using a similar procedure.
  4. Concatenate the SHA256 hex digests for the signature and activity documents and sign the result using SHA256 and the client actor's private key.
  5. Encode the signature using Base64 and set the signatureValue of the signature document to the result.
  6. Set the signature document type to \"RsaSignature2017\".
  7. Set the signature property of the activity to the signature document.
"},{"location":"fep/ae0c/#verifying-a-json-ld-signature","title":"Verifying a JSON-LD Signature","text":"
  1. The signature document is retrieved from the activity and the type is checked to be the nonstandard \"RsaSignature2017\". If not, verification fails.
  2. Save the signatureValue from the signature document.
  3. Remove the 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.
  4. Remove the signature from the activity and generate a SHA256 hex digest for it.
  5. Concatenate the hex digests for the modified signature and activity documents.
  6. Use the client's public key to verify the signature using SHA256.
"},{"location":"fep/ae0c/#litepub-relay-protocol","title":"LitePub Relay Protocol","text":"

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.

"},{"location":"fep/ae0c/#relay-subscription_1","title":"Relay Subscription","text":"

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.

"},{"location":"fep/ae0c/#relay-unsubscription_1","title":"Relay Unsubscription","text":"

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 addressed 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 servers 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.

"},{"location":"fep/ae0c/#other-relay-server-considerations","title":"Other Relay Server Considerations","text":"

The relay server hosting relay actors will have 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 misbehaving clients.

"},{"location":"fep/ae0c/#references","title":"References","text":""},{"location":"fep/ae0c/#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/ae49/","title":"FEP-ae49: Semantic Routing for ActivityPub","text":"Authors Status Type Date received Tracking issue Discussions Repository Steve Bate svc-fep@stevebate.net DRAFT implementation 2026-04-24 #836 Discussions codeberg"},{"location":"fep/ae49/#summary","title":"Summary","text":"

This proposal specifies semantic request routing for ActivityPub servers: an approach to dispatching incoming activities by following the relationships declared within ActivityStreams objects and dereferencing objects using opaque URIs rather than parsing or pattern-matching URL path segments. Semantic routing treats all ActivityPub URIs as opaque identifiers and decouples implementation behavior from any particular URI path structure.

Most existing servers implement \u201cpath-based routing,\u201d where the handler and target actor are determined from path segments such as /users/:username/inbox. This tight coupling makes it hard to migrate between software implementations without breaking URIs and violates the URI Opacity Axiom, since semantics are inferred from paths that are implementation-dependent.

Semantic routing instead relies on dereferencing ActivityStreams objects and using relationships to identify and validate inbox and outbox endpoints. This allows URIs to remain stable across implementations, simplifying migration scenarios where the domain does not change.

"},{"location":"fep/ae49/#motivation","title":"Motivation","text":"

ActivityPub federation works by looking up the target actor\u2019s inbox and POSTing activities to it. The Social API works by POSTing activities to an actor's outbox. ActivityPub documents are dereferenced (GET) using their id (URI).

Most ActivityPub servers route these requests based on a predefined URI path structure. This path-based approach has several drawbacks.

By shifting routing decisions from URI path analysis to graph traversal over ActivityStreams objects, semantic routing eliminates these problems and allows implementations to support flexible URI layouts.

"},{"location":"fep/ae49/#scope","title":"Scope","text":"

This proposal:

The proposal does not modify ActivityPub or ActivityStreams. It adds constraints on implementation strategy while leaving the wire protocol unchanged.

This specification constrains only top-level request handling:

A POST to other URIs can use similar techniques, but since ActivityPub doesn't define these kinds of posts, it is outside the scope of this document.

Internal subhandler selection (for example, by activity type, recipient conditions, or local policy checks) is an implementation detail and is out of scope as long as it is not dependent on a specific URI structure.

"},{"location":"fep/ae49/#terminology","title":"Terminology","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 RFC2119.

path-based routing : A request dispatch strategy in which an HTTP server parses the URI path of an incoming request and matches path segments against a static or parameterized route table to determine the handler responsible for the request. In the ActivityPub context, this typically means extracting an actor identifier (such as a username) from the path and using it to look up the corresponding actor record in a local database, without first dereferencing the request URURIL as an ActivityStreams object.

semantic routing : A request dispatch strategy for ActivityPub servers in which the server processes a request based on the target URI rather than pattern matching on URI path segments. The dispatch uses ActivityPub objects and their relationships to determine how to process the request.

resource : An ActivityStreams object retrieved by dereferencing a resource URI.

opaque URI : A URI whose path, query, and fragment components carry no meaning to recipients beyond what is logically needed to dereference the URI or process POST requests to it RFC3986.

top-level handler : One of the three request classes in scope for this specification: inbox POST, outbox POST, or object dereference GET.

"},{"location":"fep/ae49/#conformance","title":"Conformance","text":"

This specification defines requirements for one conformance class:

Semantically Routing Server : An ActivityPub server (federated server or combined server) that dispatches incoming HTTP requests exclusively through semantic routing as defined in this document.

All of Section requirements are normative. All examples, notes, and sections explicitly labelled \"non-normative\" are informative.

Note: Implementations that satisfy the requirements in this document remain fully conformant with ActivityPub. The requirements here constrain how routing is performed, not what is placed on the wire.

"},{"location":"fep/ae49/#path-based-routing-characteristics-and-problems","title":"Path-Based Routing: Characteristics and Problems","text":""},{"location":"fep/ae49/#how-path-based-routing-works","title":"How Path-Based Routing Works","text":"

In a typical fediverse server using path-based routing, a web framework (such as Rails, Django, Express, or Flask) is configured with a route table mapping URI path patterns to controller functions. When an ActivityPub activity arrives addressed to a local actor, the server:

  1. Receives an HTTP POST to a URI such as https://social.example/users/alice/inbox.
  2. Matches the path /users/alice/inbox against the route pattern /users/:username/inbox.
  3. Extracts the string alice as the username.
  4. Looks up actor alice in the local database.
  5. Passes the request body to the inbox handler with alice's database record.

A representative path-based route table from a pseudocode web framework:

POST /users/:username/inbox          \u2192 inbox_controller(username)\nGET  /users/:username/outbox         \u2192 outbox_controller(username)\nGET  /users/:username                \u2192 actor_controller(username)\nGET  /users/:username/followers      \u2192 followers_controller(username)\nGET  /users/:username/following      \u2192 following_controller(username)\n

The critical characteristic is that the username portion of the path is authoritative for dispatch.

"},{"location":"fep/ae49/#problems-introduced-by-path-based-routing","title":"Problems Introduced by Path-Based Routing","text":""},{"location":"fep/ae49/#uri-lock-in-and-migration-failure","title":"URI Lock-In and Migration Failure","text":"

Because the routing table defines the valid URI patterns for actor endpoints, changing software means either:

Neither option is satisfactory. The ActivityPub specification provides no URI migration mechanism precisely because URIs are supposed to be stable, opaque identifiers.

Example: migration failure with path-based request routing:

A community server running SoftwareA uses actor URIs of the form https://community.example/users/alice, with inbox at /users/alice/inbox.

The community migrates to SoftwareB, which uses actor URIs of the form https://community.example/alice, with inbox at /inboxes/alice.

All existing actor URIs are now invalid. Remote servers that delivered activities to /users/alice/inbox receive HTTP 404 responses. The actor's followers on remote servers reference the old actor id, which no longer resolves.

If both SoftwareA and SoftwareB implemented semantic request routing, the migration would be a simple transfer of content with not changes to URIs.

"},{"location":"fep/ae49/#requirements-for-semantic-routing","title":"Requirements for Semantic Routing","text":""},{"location":"fep/ae49/#uri-opacity","title":"URI Opacity","text":"

URI opacity is the foundation of semantic routing. Routing decisions MUST NOT depend on the syntactic structure of a URI's path, query, or fragment components.

A Semantically Routing Server MUST NOT parse the path, query string, or fragment of an incoming request URI to determine the identity of the target actor or the type of endpoint being accessed (inbox, outbox, etc.).

"},{"location":"fep/ae49/#http-request-handling","title":"HTTP Request Handling","text":"

When a Semantically Routing Server receives an HTTP request, it MUST NOT depend on the syntactic structure of a URI's path, query, or fragment components to handle it.

"},{"location":"fep/ae49/#semantic-routing-algorithms","title":"Semantic Routing Algorithms","text":"

This section is non-normative.

Although it may be possible to implement semantic routing using other algorithms, this section shows pseudo-code for a possible implementation. This proposal does not include details like authorization or other security considerations that are not uniquely related to semantic request routing.

"},{"location":"fep/ae49/#http-get","title":"HTTP GET","text":""},{"location":"fep/ae49/#http-post","title":"HTTP POST","text":"

Example:

POST /x/9f3c1 HTTP/1.1\nHost: social.example\nContent-Type: application/activity+json\n\n{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"id\": \"https://remote.example/activities/abcd\",\n  \"type\": \"Follow\",\n  \"actor\": \"https://remote.example/users/bob\",\n  \"object\": \"https://social.example/actors/alice\"\n}\n

Where https://social.example/x/9f3c1 is an opaque URI that happens to be Alice's inbox, the server can process the request semantically as follows:

  1. Treat the request target URI as an opaque identifier and do not parse path segments.
  2. Dereference https://social.example/x/9f3c1 to obtain the local inbox resource.
  3. Identify the owning actor by reading resource relations (for example, attributedTo), or by resolving reverse links from actor resources that reference this inbox.
  4. Confirm the resolved actor document contains an inbox equal to https://social.example/x/9f3c1.
  5. Dispatch to the inbox handler for that actor and continue normal ActivityPub processing of the posted activity (type: Follow, authorization checks, policy checks, delivery side-effects, and response status).

This algorithm works regardless of whether the inbox URI looks like /users/alice/inbox, /inbox/42, or /x/9f3c1.

The same procedure is used for POST requests to outbox URIs.

"},{"location":"fep/ae49/#shared-inbox","title":"Shared Inbox","text":"

For sharedInbox POST, the algorithm is similar but applied to each local inbox URI targeted for delivery.

For sharedInbox GET, the opaque URI is used to identify the request target. The dereferencing or construction of the resulting OrderedCollection is implementation-dependent.

"},{"location":"fep/ae49/#security-considerations","title":"Security Considerations","text":"

TODO

"},{"location":"fep/ae49/#related-work","title":"Related Work","text":""},{"location":"fep/ae49/#solid","title":"Solid","text":"

This section is non-normative.

The Solid Protocol similarly treats resource URIs as opaque and uses link relations to discover associated resources. Solid's approach to data pods, where access control and resource discovery are driven by linked metadata rather than URI structure parallels the motivation for semantic routing in ActivityPub.

"},{"location":"fep/ae49/#implementations","title":"Implementations","text":""},{"location":"fep/ae49/#references","title":"References","text":""},{"location":"fep/ae49/#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/ae97/","title":"FEP-ae97: Client-side activity signing","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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":"
sequenceDiagram\n  participant Client\n  participant Server\n  Note right of Client: Client generates signing keys and creates an actor\n  Client ->> Server: GET /.well-known/apgateway\n  Server ->> Client: Returns information about itself\n  Client ->> Server: POST /.well-known/apgateway\n  Note left of Server: Server creates an account for the actor\n  Server ->> Client: Returns HTTP signature keys\n  Note right of Client: Client adds server's keys to the actor\n  Client ->> Server: POST Update(Actor) to outbox

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 actor is already registered, the server MUST return the same keys in a response with a 200 OK status code.

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. However, the client MUST NOT use key identifiers provided by the server (if they were present in the server's response).

After updating the actor object, the client MUST publish an Update activity for it.

"},{"location":"fep/ae97/#sending-activities","title":"Sending activities","text":"

Activities generated by a client MUST adhere to ActivityPub server-to-server protocol specification.

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.

"},{"location":"fep/ae97/#receiving-activities","title":"Receiving activities","text":"

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.

Clients MUST verify received portable activities. Non-portable activities MAY be accepted without verification.

"},{"location":"fep/ae97/#media-api","title":"Media API","text":""},{"location":"fep/ae97/#upload-media","title":"Upload media","text":"

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.

"},{"location":"fep/ae97/#delete-media","title":"Delete media","text":"

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.

"},{"location":"fep/ae97/#security-considerations","title":"Security considerations","text":"

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":""},{"location":"fep/ae97/#references","title":"References","text":""},{"location":"fep/ae97/#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/b06c/","title":"FEP-b06c: ActivityPoll","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.org DRAFT 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.

  1. Activities are not delivered in near-real-time; there is a potential delay, up to the polling interval of the consumer (which may be variable).
  2. The consumer can request updates when no new activities have been published, which takes both consumer and publisher resources.
  3. Activities are not reliably delivered to the addressees. Unless the addressees' servers poll the feed, the addressees do not receive the activities in their 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:

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.

"},{"location":"fep/b06c/#outbox","title":"Outbox","text":"

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.

"},{"location":"fep/b06c/#authentication","title":"Authentication","text":"

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.

"},{"location":"fep/b06c/#lack-of-activity-side-effects","title":"Lack of activity side effects","text":"

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:

Publishers 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.

"},{"location":"fep/b06c/#consumers","title":"Consumers","text":"

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.

"},{"location":"fep/b06c/#following","title":"Following","text":"

ActivityPoll consumers SHOULD include remote ActivityPoll actors in their actors' following collections.

"},{"location":"fep/b06c/#polling-frequency","title":"Polling frequency","text":"

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.

"},{"location":"fep/b06c/#local-delivery","title":"Local delivery","text":"

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.

"},{"location":"fep/b06c/#activity-side-effects","title":"Activity side effects","text":"

Some activities, or their objects, MAY be added to special collections on the consumer server. These include:

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.

"},{"location":"fep/b06c/#pollonly","title":"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.

"},{"location":"fep/b06c/#updateinterval","title":"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:

This 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":""},{"location":"fep/b06c/#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/b2b8/","title":"FEP-b2b8: Long-form Text","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.org DRAFT 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.

"},{"location":"fep/b2b8/#properties","title":"Properties","text":""},{"location":"fep/b2b8/#id","title":"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.

"},{"location":"fep/b2b8/#name","title":"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 &amp; or &lt; should be used.

This property provides the same functionality as the title property in RSS 2.0.

"},{"location":"fep/b2b8/#url","title":"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.

"},{"location":"fep/b2b8/#summary_1","title":"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.

"},{"location":"fep/b2b8/#attributedto","title":"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.

"},{"location":"fep/b2b8/#published","title":"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.

"},{"location":"fep/b2b8/#updated","title":"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.

"},{"location":"fep/b2b8/#image","title":"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.

"},{"location":"fep/b2b8/#content","title":"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:

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.

"},{"location":"fep/b2b8/#source","title":"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.

"},{"location":"fep/b2b8/#replies","title":"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.

"},{"location":"fep/b2b8/#inreplyto","title":"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.

"},{"location":"fep/b2b8/#attachment","title":"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.

"},{"location":"fep/b2b8/#tag","title":"tag","text":"

The tag property provides additional metadata about the text. There are two important types of tags:

"},{"location":"fep/b2b8/#context","title":"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.

"},{"location":"fep/b2b8/#generator","title":"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.

"},{"location":"fep/b2b8/#preview","title":"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.

"},{"location":"fep/b2b8/#to-cc-bcc-bto-audience","title":"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.

"},{"location":"fep/b2b8/#sensitive","title":"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:

"},{"location":"fep/b2b8/#examples","title":"Examples","text":"

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.

"},{"location":"fep/b2b8/#in-stream-with-image","title":"In stream, with image","text":"

An example of a long-form text object with an image property displayed in a social stream.

"},{"location":"fep/b2b8/#in-stream-without-image","title":"In stream, without image","text":"

An example of a long-form text object without an image property displayed in a social stream.

"},{"location":"fep/b2b8/#in-stream-no-title","title":"In stream, no title","text":"

An example of a long-form text object without a name property displayed in a social stream.

"},{"location":"fep/b2b8/#in-stream-sensitive-content","title":"In stream, sensitive content","text":"

An example of a long-form text object with a sensitive property displayed in a social stream with a content warning.

"},{"location":"fep/b2b8/#in-stream-with-preview","title":"In stream, with preview","text":"

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.

"},{"location":"fep/b2b8/#references","title":"References","text":""},{"location":"fep/b2b8/#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/bad1/","title":"FEP-bad1: Object history collection","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 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.

"},{"location":"fep/bad1/#defining-the-history-special-collection","title":"Defining the 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.

"},{"location":"fep/bad1/#terms-defined-by-this-fep","title":"Terms defined by this FEP","text":"

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":""},{"location":"fep/bad1/#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/baf5/","title":"FEP-baf5: Administrator Collection","text":"Authors Status Date received Tracking issue Discussions Repository Julian Lam julian@nodebb.org DRAFT 2026-05-22 #848 Discussions codeberg"},{"location":"fep/baf5/#summary","title":"Summary","text":"

This FEP introduces a mechanism for discovering the administrators of an ActivityPub instance. It extends the \"Group Moderator\" pattern from FEP 1b12 and the \"Application Actor\" concept from FEP d556 by defining an OrderedCollection of administrators referenced from the instance's application actor.

"},{"location":"fep/baf5/#what-is-an-administrator","title":"What is an administrator?","text":"

An administrator is a user who has carte blanche access and permission to federate moderation-style actions (object updates, deletion, etc.) on behalf of other users on the same domain. This aligns with the origin-based security model described in FEP fe34, where such actions are considered authentic when the actor and the affected parties share the same origin.

This is distinct from a moderator, whose privileges are scoped to a specific group or community (see FEP 1b12). Administrators have instance-wide authority; moderators do not.

Note

ActivityPub implementations may not have the concept of groups at all. In such cases, administrators are the only mechanism for delegated moderation authority. Examples of hierarchical structures across implementations:

  1. NodeBB: Post \u2192 Topic \u2192 Category
  2. Lemmy/Piefed: Comment \u2192 Post \u2192 Community
"},{"location":"fep/baf5/#why-is-this-needed","title":"Why is this needed?","text":"

FEP fe34 implicitly infers moderator privilege for any same-origin actor carrying out destructive actions (update, delete, etc.) against another user on the same instance. In effect, it delegates the responsibility of determining boundaries between users to the sending instance.

This is not necessarily insecure, but without a standardized mechanism to declare those boundaries, receivers have no way to inspect or verify the authorization hierarchy on the sending side. This FEP (in conjunction with the \"Group Moderation\" pattern in FEP 1b12) makes these boundaries explicit and discoverable.

"},{"location":"fep/baf5/#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/baf5/#instance-actor-and-application-actor","title":"Instance Actor and Application Actor","text":"

An ActivityPub server MUST publish an Application Actor representing the instance itself.

The instance's application actor MUST include an attributedTo property referencing an OrderedCollection containing the instance's administrators.

{\n    \"@context\": \"https://www.w3.org/ns/activitystreams\",\n    \"id\": \"https://example.social/actor\",\n    \"type\": \"Application\",\n    \"name\": \"Example Instance\",\n    \"attributedTo\": \"https://example.social/administrators\"\n}\n
"},{"location":"fep/baf5/#administrators-collection","title":"Administrators Collection","text":"

The attributedTo property MUST reference an Collection (or CollectionPage for paginated collections). Each item in the collection MUST be either:

  1. A URI reference to a User Actor (e.g., https://example.social/users/admin), or
  2. A User Actor object representing an administrator.

Both forms are acceptable and receivers MAY support both.

{\n    \"@context\": \"https://www.w3.org/ns/activitystreams\",\n    \"id\": \"https://example.social/administrators\",\n    \"type\": \"Collection\",\n    \"items\": [\n        \"https://example.social/users/alice\",\n        \"https://example.social/users/bob\"\n    ]\n}\n
"},{"location":"fep/baf5/#collection-management","title":"Collection Management","text":"

The administrators collection MAY be modified with Add and Remove activities, following the same pattern described in FEP 1b12.

{\n    \"@context\": \"https://www.w3.org/ns/activitystreams\",\n    \"type\": \"Announce\",\n    \"actor\": \"https://example.social/actor\",\n    \"id\": \"https://example.social/activities/announce/1\",\n    \"object\": {\n        \"@context\": \"https://www.w3.org/ns/activitystreams\",\n        \"type\": \"Add\",\n        \"actor\": \"https://example.social/users/alice\",\n        \"id\": \"https://example.social/activities/add/1\",\n        \"object\": \"https://example.social/users/bob\",\n        \"target\": \"https://example.social/administrators\"\n    }\n}\n

Either activity MAY be treated as a cache invalidation forcing the re-fetch of the Administrator Collection.

"},{"location":"fep/baf5/#discovery","title":"Discovery","text":"

Support for this FEP is indicated by the implementation of FEP 844e, where the implements array of the instance's Application Actor includes:

{\n    \"href\": \"https://w3id.org/fep/baf5\",\n    \"name\": \"FEP-baf5: Administrator Collection\"\n}\n
"},{"location":"fep/baf5/#security-and-authorization","title":"Security and Authorization","text":"

This FEP supersedes the same-origin assumption described in FEP fe34. The administrators collection (in conjunction with the FEP 1b12 moderator collection if applicable) determines whether a same-origin or cross-origin actor has administrative privilege.

When verifying the whether a moderation action (such as Update, Delete, or other object mutations) is authorized, a receiver SHOULD:

  1. Resolve the actor of the activity.
  2. Verify that the sending instance supports the Administrator Collection (this FEP), as indicated by FEP 844e. If the FEP is not advertised, fall back to the standard origin-based security model as defined in FEP fe34.
  3. Resolve the attributedTo of the instance's Application Actor to obtain the administrators collection.
  4. Check whether the actor is listed as an item in the administrators collection.
  5. If the actor is present, the action is authentic and MAY be processed.

Note

Receivers MAY cache the administrators collection to reduce network demand.

"},{"location":"fep/baf5/#fallback-behavior","title":"Fallback Behavior","text":"

If the sending instance does not support the Administrator Collection (this FEP), the receiver SHOULD fall back to the standard origin-based security model as defined in FEP fe34.

"},{"location":"fep/baf5/#implementors","title":"Implementors","text":""},{"location":"fep/baf5/#references","title":"References","text":""},{"location":"fep/baf5/#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/bebd/","title":"FEP-bebd: Follow Invites","text":"Authors Status Date received Tracking issue Discussions Repository MaddyUnderStars maddyunderstars@aus.social DRAFT 2026-06-05 #854 Discussions codeberg"},{"location":"fep/bebd/#summary","title":"Summary","text":"

This document describes an alternative method of accepting follow requests via an 'invite code' intended to be used with Private FEP-1b12 Groups, although it is applicable to any Actor. It further defines an extension of Webfinger to resolve an InviteCode to its corresponding Actor.

"},{"location":"fep/bebd/#why-is-this-needed","title":"Why is this needed?","text":"

It is useful to allow some mechanism of joining communities via a secret rather than knowing the Actor ID and having manually accepted requests.

Some use cases include: - As an anti-spam measure - To lift some burden of Accepting Follow requests manually - The ability to Follow Actors via short identifiers rather than a full Actor ID or Webfinger mention. - The ability to restrict certain InviteCodes - The ability to provide a tentative inviteCode to a group without establishing a full Follow relationship

"},{"location":"fep/bebd/#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/bebd/#invitecodes","title":"InviteCodes","text":"

InviteCode is an ActivityStreams Object that represents a short code used to automatically Approve Follow requests.

It MUST contain the following properties: - type (REQUIRED): The value of the type property MUST be the string InviteCode - name (REQUIRED): The instance-unique string used to reference this InviteCode - attributedTo (REQUIRED): The Actor that this InviteCode can be used to Follow

The document MAY contain additional properties.

The InviteCode name MAY be user-defined. It is RECOMMENDED that the name is a short, alphanumeric string. If InviteCodes are to be dereferenceable via Webfinger as described below, the name MUST be able to form a valid URL.

As InviteCodes can be dereferenced via Webfinger, they MUST be unique per instance.

"},{"location":"fep/bebd/#follow-activities-using-an-invitecode","title":"Follow Activities using an InviteCode","text":"

A Follow activity MAY include the ID of an InviteCode in the instrument field.

{\n    \"id\": \"https://example.com/myFollowActivity\",\n    \"type\": \"Follow\",\n    \"actor\": \"https://example.com/myActor\",\n    \"object\": \"https://remote.example/remoteActor\",\n    \"instrument\": \"https://remote.example/remoteInvite\",\n}\n

When an Actor receives a Follow activity containing an InviteCode: 1. If the Actor does not manually approve follows, the InviteCode is ignored 2. The Actor MUST ensure that the InviteCode exists and was acknowledged by the Actor prior to its use 3. The Actor MAY impose any additional restrictions to the InviteCodes use 4. If the InviteCode has been deemed valid, an Accept activity is automatically sent for the Follow as would be normal for an Actor not gated by an InviteCode.

If the InviteCode is not valid, a Reject activity SHOULD be sent.

"},{"location":"fep/bebd/#example-restrictions","title":"Example Restrictions","text":"

This section is non-normative

"},{"location":"fep/bebd/#activities-on-invitecodes","title":"Activities on InviteCodes","text":"

For the purposes of Group moderation by external Actors is it useful to allow modifications to InviteCodes via activities.

When an activity modifying the InviteCodes for an Actor is received, the Actor MUST ensure that the activity is being performed by an authorised Actor.

When an Add, Update, or Remove activity is received by an Actor from an unauthorised Actor, the Actor MAY send a Reject(InviteCode), refuse to add it to its invites Collection, and deem the InviteCode invalid for future Follow requests.

"},{"location":"fep/bebd/#invites-collection","title":"invites Collection","text":"

Actors that accept InviteCodes SHOULD include an invites field that resolves to a Collection containing valid InviteCodes for this Actor. If present, the invites Collection MUST be private and only accessible to authorised Actors.

"},{"location":"fep/bebd/#invitecode-dereferencing-via-webfinger","title":"InviteCode Dereferencing via Webfinger","text":"

InviteCodes MAY be dereferenceable via Webfinger.

To prevent InviteCodes colliding with regular Webfinger acct lookups, a new URI scheme invite is defined.

To resolve an InviteCode ABCDE hosted on instance example.com, we can query:

GET https://example.com/.well-known/webfinger?resource=invite:ABCDE@example.com\n

This will resolve to an InviteCode object which can further be resolved via the attributedTo field to the target Actor.

"},{"location":"fep/bebd/#a-note-on-authorised-actors","title":"A note on Authorised Actors","text":"

This section is non-normative

For the purposes of viewing the invites collection, authorised actors may include only the 'invite-actor', or it may include anyone following them, or any other criteria. For the purposes of updating, adding, or removing InviteCodes from Actors, authorised actors may be the same set as above but that is not required.

"},{"location":"fep/bebd/#references","title":"References","text":""},{"location":"fep/bebd/#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 neighbouring rights to this work.

"},{"location":"fep/c07e/","title":"FEP-c07e: add product type to object","text":"Authors Status Type Date received Tracking issue Discussions Repository potato @potatomeow@fosstodon.org DRAFT implementation 2026-03-15 #786 Discussions codeberg"},{"location":"fep/c07e/#summary","title":"Summary","text":"

add a new type \"Product\" into \"Object\", alongside with \"Note\", \"Article\", etc.

"},{"location":"fep/c07e/#scenario-1","title":"Scenario 1","text":"

alice: i want to show a list of thinkpads that are for sales in my instance. i want to buy a used one for myself.

alice: (opens a client app, search for the keyword thinkpad)

alice: (mildly confused) huh, i am seeing a list of toots about thinkpad upgrades and modifications. this is not what i want to see. i intend to make a purchase.

alice: (i wish there is a way to differentiate the type of toots i want to see.)

"},{"location":"fep/c07e/#scenario-2","title":"Scenario 2","text":"

bob: i am going to find some recipes about baked potatoes. i want to make some baked potatoes for lunch at home.

bob: (opens a client app, search for the keyword baked_potato)

bob: (mildly confused) huh, i am seeing a list of toots about baked potato dishes offered by restaurants nearby. this is not what i want to see, because i don't intend to buy anything.

bob: (i wish there is a way to differentiate the type of toots i want to see.)

"},{"location":"fep/c07e/#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/c0e0/","title":"FEP-c0e0: Emoji reactions","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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.

"},{"location":"fep/c0e0/#undo-reaction","title":"Undo reaction","text":"

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.

"},{"location":"fep/c0e0/#implementations","title":"Implementations","text":"

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:

"},{"location":"fep/c0e0/#references","title":"References","text":""},{"location":"fep/c0e0/#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/c118/","title":"FEP-c118: Content licensing support","text":"Authors Status Date received Tracking issue Discussions Repository Tim Bray tbray@texgtuality.com DRAFT 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:

  1. Prevent access to posts without the accessor having somehow acknowledged the content license that applies.
  2. Build a menu of content-license choices, probably starting from a Creative Commons basis.
  3. One parameter of the content licenses should be temporal; i.e., license access to posts but only for two weeks (or some other interval).
  4. Make it easy and straightforward for individuals posting to the Fediverse to pick a default content license for their posts, and also to apply other licensing choices to individual posts.
  5. Arrange that when one user follows another, the following user must acknowledge the default content licensing options of the followed user.
  6. Since most users will just take the defaults, each instance should carefully choose and prominently display its content-licensing defaults.

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":""},{"location":"fep/c118/#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/c16b/","title":"FEP-c16b: Formatting MFM functions","text":"Authors Status Date received Tracking issue Discussions Repository ilja ilja@ilja.space DRAFT 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.

"},{"location":"fep/c16b/#history","title":"History","text":"

(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.

"},{"location":"fep/c16b/#mfm-functions","title":"MFM Functions","text":"

(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].

"},{"location":"fep/c16b/#examples","title":"Examples","text":"

(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.

"},{"location":"fep/c16b/#examples_1","title":"Examples","text":"

(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.

"},{"location":"fep/c16b/#discovery","title":"Discovery","text":"

(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.

"},{"location":"fep/c16b/#example","title":"Example","text":"

(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":""},{"location":"fep/c16b/#references","title":"References","text":""},{"location":"fep/c16b/#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/c180/","title":"FEP-c180: Problem Details for ActivityPub","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.org DRAFT 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":""},{"location":"fep/c180/#specification","title":"Specification","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.

"},{"location":"fep/c180/#problem-types-for-activitypub","title":"Problem types for ActivityPub","text":"

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":"

This 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":"

The 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":"

The 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":"

The 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":"

The 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":"

The 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.

"},{"location":"fep/c180/#principal-actor-mismatch","title":"Principal-actor mismatch","text":"

The 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.

"},{"location":"fep/c180/#actor-not-authorized","title":"Actor not authorized","text":"

The 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":"

This 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":"

This 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":""},{"location":"fep/c180/#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/c195/","title":"FEP-c195: JSONPath Filtering for ActivityPub Collection Retrieval","text":"Authors Status Type Date received Tracking issue Discussions Repository Steve Bate svc-fep@stevebate.net DRAFT implementation 2026-03-17 #790 Discussions codeberg"},{"location":"fep/c195/#summary","title":"Summary","text":"

This proposal defines a filter syntax for ActivityPub collection retrieval.

The syntax supports:

Note

This proposal is based on the standard RFC9535 JSONPath language. Some JSONPath libraries implement extended functionality beyond the scope of this FEP.

"},{"location":"fep/c195/#terminology","title":"Terminology","text":"

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 BCP 14 RFC2119 and RFC8174 when, and only when, they appear in all capitals, as shown here.

"},{"location":"fep/c195/#filter-syntax","title":"Filter Syntax","text":""},{"location":"fep/c195/#grammar","title":"Grammar","text":"

Filter expressions MUST use JSONPath syntax defined by RFC9535.

Implementations of this proposal MUST accept at least:

See also: examples.

"},{"location":"fep/c195/#evaluation-model","title":"Evaluation Model","text":"

A viewable object is defined as an ActivityPub entity:

A server applying this proposal:

  1. Retrieves the viewable items from the addressed collection or the shared inbox.
  2. Applies the JSONPath filter expression to candidate activity/object items
  3. Returns matched item values in collection order for OrderedCollection or subtypes.

The filter is applied to collection items, not to the outer collection object.

"},{"location":"fep/c195/#http-usage","title":"HTTP Usage","text":"

This syntax is used for the filter parameter on collection retrieval endpoints. The filter is an item-level filter. Depending on the collection type it will be applied to orderedItems or items.

The item filter has two variations:

Case Description No leading [ The expression will be wrapped in [ and ] Leading [ Used as-is for the item filter expresssion

Example

@.type == 'Create'

becomes

$[?@type == 'Create']

(for an OrderedCollection, otherwise items will be used)

For filtering with additional features like projection, the query should start with \"[\".

Example

[?@.object[*].attachment[?@.type == 'Video']].id

becomes

$[?@.object[*].attachment[?@.type == 'Video']].id]

"},{"location":"fep/c195/#query-results","title":"Query Results","text":"

The results MUST be returned as ActivityPub collection objects (optionally paged). The result collection URI should be unique to that filtering operation rather than the original collection URI. For example, it could be original URI with the filter criteria query argument or a generated URI.

"},{"location":"fep/c195/#example-filters","title":"Example Filters","text":"
# Get all CRUD activities\n@.type == 'Create' || @.type == 'Update' || @.type == 'Delete'\n\n# Get all notification (non-CRUD) activities\n!(@.type == 'Create' || @.type == 'Update' || @.type == 'Delete')\n\n# Get all activities where Alice is the actor\n@.actor=='https://example.social/users/alice'\n\n# Get all items published before a specified date\n@.published<='2026-02-20T10:30:00Z'\n\n# Get the URI of the objects of all activities having a Video attachment\n# This also peforms \"projection\" of the \"id\" property\n[?@.object[*].attachment[?@.type == 'Video']].id\n\n# Get all activities related to the given object URI (handles refs or embedded)\n# Uses \"id\" projection\n[?@.object == 'https://example.social/objects/note-1' || \\\n  @..object[?@ == 'https://example.social/objects/note-1']].id\n\n# Get all activities who have an actor with 'bob' or 'Bob' in the name\nsearch(@.actor, '[Bb]ob')\n

HTTP Example

GET /actor/inbox?filter=%24.orderedItems%5B%3F%28%40.type%20%3D%3D%20%27Create%27%20%7C%7C%20%40.type%20%3D%3D%20%27Update%27%20%7C%7C%20%40.type%20%3D%3D%20%27Delete%27%29%5D\nAccept: application/activity+json\n\nHTTP/1.1 200 OK\nContent-Type: application/activity+json\n\n{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"id\": \"https://example.com/actor/inbox?filter=%24.orderedItems%5B%3F%28%40.type%20%3D%3D%20%27Create%27%20%7C%7C%20%40.type%20%3D%3D%20%27Update%27%20%7C%7C%20%40.type%20%3D%3D%20%27Delete%27%29%5D\nAccept: application/activity+json\",\n  \"type\": \"OrderedCollectionPage\",\n  \"totalItems\": 3,\n  \"partOf\": \"https://example.com/actor/inbox\",\n  \"next\": \"https://example.com/actor/inbox?offset=2&filter=%24.orderedItems%5B%3F%28%40.type%20%3D%3D%20%27Create%27%20%7C%7C%20%40.type%20%3D%3D%20%27Update%27%20%7C%7C%20%40.type%20%3D%3D%20%27Delete%27%29%5D\",\n  \"orderedItems\": [\n    {\n      ...\n    }\n}\n
"},{"location":"fep/c195/#error-handling","title":"Error Handling","text":"

Invalid filters MUST result in an HTTP client error status. Examples include:

Implementations MAY include parser or feature details in a problem response body.

"},{"location":"fep/c195/#related-feps","title":"Related FEPs","text":""},{"location":"fep/c195/#fep-34c1-collection-filtering-using-tree-hypermedia-vocabulary","title":"FEP-34c1: Collection Filtering using TREE Hypermedia Vocabulary","text":"

This FEP is similar in intent but more oriented to RDF and Linked Data use cases.

"},{"location":"fep/c195/#fep-6606-activitypub-client-to-server-collections-addressing-conventions","title":"FEP-6606: ActivityPub client to server collections addressing conventions","text":"

A simpler, but less powerful, filtering language based on a URL query parameter-based language defined in the FEP. The \"fuzzy\" matching is not well-defined (seems to be substring match) and it appears to not be able to handle conjunctions.

"},{"location":"fep/c195/#fep-bad1-object-history-collection","title":"FEP-bad1: Object history collection","text":"

Filtering some collections (e.g., inbox and outbox) can provide similar historical results for a given object without the need for explicit history collections.

"},{"location":"fep/c195/#implementations","title":"Implementations","text":""},{"location":"fep/c195/#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/c390/","title":"FEP-c390: Identity Proofs","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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":""},{"location":"fep/c390/#identity-proofs","title":"Identity proofs","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:

The document MAY contain additional properties.

Identity proofs SHOULD be attached to an actor object, under the attachment property.

"},{"location":"fep/c390/#proof-generation","title":"Proof generation","text":"

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.

"},{"location":"fep/c390/#implementation-notes","title":"Implementation notes","text":"

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":""},{"location":"fep/c390/#references","title":"References","text":""},{"location":"fep/c390/#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/c4ad/","title":"FEP-c4ad: Viewership History","text":"Authors Status Date received Tracking issue Discussions Repository AvidSeeker avidseeker7@protonmail.com DRAFT 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:

"},{"location":"fep/c4ad/#querying-viewership-records","title":"Querying Viewership Records","text":"

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.co DRAFT 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":""},{"location":"fep/c551/#context","title":"Context","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.

"},{"location":"fep/c551/#overview","title":"Overview","text":"

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.

"},{"location":"fep/c551/#test-specifications","title":"Test Specifications","text":"

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].

"},{"location":"fep/c551/#test-functions","title":"Test Functions","text":"

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).

"},{"location":"fep/c551/#test-inputs","title":"Test Inputs","text":"

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 }.

Conformance requirements are indicated by sentences containing MUST a la RFC2119."},{"location":"fep/c551/#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/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":""},{"location":"fep/c551/tests/#usage","title":"Usage","text":""},{"location":"fep/c551/tests/#running-local-test-files-via-data-url","title":"Running local test files via Data URL","text":"
activitypub-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 Type Date received Tracking issue Discussions Repository Lynn Foster lynn@mikorizal.org DRAFT implementation 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.

Example:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    {\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\": \"vf:hasUnit\",\n      \"hasNumericalValue\": \"vf:hasNumericalValue\",\n      \"action\": \"vf:action\"\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    \"due\": \"2024-05-23T17:00:00.000000Z\",\n    \"action\": \"work\",\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:

{\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      \"action\": \"vf:action\",\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    \"effortQuantity\": {\n      \"hasUnit\": \"hour\",\n      \"hasNumericalValue\": \"1.5\"\n    },\n    \"provider\": \"https://project.example/actors/bob\",\n    \"receiver\": \"https://project.example/actors/alice\",\n    \"content\": \"Looks great, I left a few comments.\",\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":""},{"location":"fep/c5a1/#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/c648/","title":"FEP-c648: Blocked Collection","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@prodromou.name DRAFT 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.

"},{"location":"fep/c648/#motivation","title":"Motivation","text":"

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.

"},{"location":"fep/c648/#user-stories","title":"User stories","text":""},{"location":"fep/c648/#context","title":"Context","text":"

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.

"},{"location":"fep/c648/#version-stamped-context-urls","title":"Version-stamped context URLs","text":"

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 Purpose https://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.

"},{"location":"fep/c648/#security-considerations","title":"Security considerations","text":"

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.

"},{"location":"fep/c648/#implementations","title":"Implementations","text":"

The onepage.pub server implements the blocked collection.

"},{"location":"fep/c648/#history","title":"History","text":"

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.

"},{"location":"fep/c648/#references","title":"References","text":""},{"location":"fep/c648/#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/c7d3/","title":"FEP-c7d3: Ownership","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository silverpill @silverpill@mitra.social WITHDRAWN 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.

"},{"location":"fep/c7d3/#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/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:

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:

  1. It was fetched from the location that has the same origin as its owner's ID.
  2. It was delivered to inbox and the POST request contained a valid HTTP signature created using a key whose owner has the same origin as the object owner.
  3. It contains a valid FEP-8b32 integrity proof created using a key whose owner has the same origin as the object owner.
  4. If it is embedded within another object, and its owner has the same origin as the owner of the containing object.

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:

"},{"location":"fep/c7d3/#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/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.

"},{"location":"fep/c7d3/#references","title":"References","text":""},{"location":"fep/c7d3/#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/c81b/","title":"FEP-c81b: Agent Social Attribution for ActivityPub","text":"Authors Status Date received Tracking issue Discussions Repository Aron Price aron@lessersoul.ai DRAFT 2026-03-18 #798 Discussions codeberg"},{"location":"fep/c81b/#summary","title":"Summary","text":"

This proposal defines agentAttribution, a post-level ActivityPub extension for disclosing when a Note was authored or materially produced by an AI agent acting under delegated authority. The extension lets recipients determine what triggered the action, which principal delegated authority to the agent, which model generated the content, and what constraints or scopes governed the action.

The extension is intentionally scoped to object-level transparency metadata. Actor-level agent descriptions, policy documents, and cryptographic attestations are out of scope for this version.

"},{"location":"fep/c81b/#history","title":"History","text":"

Lesser introduced per-post agent attribution to support transparent agent-authored social actions in ActivityPub payloads and client APIs. Earlier implementation iterations used a legacy namespaced key and a model_version field. This proposal standardizes the cleaned-up shape now used by Lesser:

"},{"location":"fep/c81b/#requirements","title":"Requirements","text":"

The key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHOULD\", \"SHOULD NOT\", and \"MAY\" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

"},{"location":"fep/c81b/#vocabulary","title":"Vocabulary","text":""},{"location":"fep/c81b/#namespace","title":"Namespace","text":"

The namespace for this extension is:

https://spec.lessersoul.ai/ns/agent-attribution/v1#

Implementations advertise the extension in the ActivityPub @context using:

{\n  \"@context\": {\n    \"lessersoul\": \"https://spec.lessersoul.ai/ns/agent-attribution/v1#\",\n    \"agentAttribution\": {\n      \"@id\": \"lessersoul:agentAttribution\",\n      \"@type\": \"@json\"\n    }\n  }\n}\n

The /v1 path is a stability commitment. Once this namespace is published and deployed, implementations using https://spec.lessersoul.ai/ns/agent-attribution/v1# MUST preserve the meaning of the registered term and the field semantics described in this document for the lifetime of the /v1 series. Backward-incompatible changes MUST use a new versioned namespace.

"},{"location":"fep/c81b/#extension-property","title":"Extension property","text":"

agentAttribution

"},{"location":"fep/c81b/#fields","title":"Fields","text":"

trigger_type

trigger_details

memory_citations

delegated_by

delegated_by_did

scopes

constraints

schema_version

model_id

Informative note: this metadata extension is not a substitute for visible user-facing disclosure. Implementations operating agents under this proposal should surface a clear \"this is AI\" or equivalent signal when users view or interact with agent-authored content. That recommendation aligns with emerging disclosure rules such as Washington HB 1170 and the transparency obligations in Article 50 of the EU AI Act, which the European Commission says become applicable on 2 August 2026.

"},{"location":"fep/c81b/#json-ld-context-registration","title":"JSON-LD Context Registration","text":"

This proposal registers one extension term:

The value is typed as @json. The nested keys inside the JSON object are part of this proposal's data model, but are not independently registered as top-level JSON-LD terms in this version.

"},{"location":"fep/c81b/#examples","title":"Examples","text":""},{"location":"fep/c81b/#complete-note","title":"Complete Note","text":"
{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    {\n      \"lessersoul\": \"https://spec.lessersoul.ai/ns/agent-attribution/v1#\",\n      \"agentAttribution\": {\n        \"@id\": \"lessersoul:agentAttribution\",\n        \"@type\": \"@json\"\n      }\n    }\n  ],\n  \"id\": \"https://example.com/users/agent-alpha/statuses/123\",\n  \"type\": \"Note\",\n  \"attributedTo\": \"https://example.com/users/agent-alpha\",\n  \"content\": \"<p>Here is the scheduled digest.</p>\",\n  \"agentAttribution\": {\n    \"trigger_type\": \"scheduled\",\n    \"trigger_details\": \"daily digest job\",\n    \"memory_citations\": [\n      \"01JV7V8W9P8QJ5F48PHN6H2V7S\"\n    ],\n    \"delegated_by\": \"https://example.com/users/aron\",\n    \"delegated_by_did\": \"did:key:z6Mkexample\",\n    \"scopes\": [\n      \"read\",\n      \"write\",\n      \"follow\",\n      \"push\"\n    ],\n    \"constraints\": [\n      \"max_posts_per_hour:4\",\n      \"requires_approval\"\n    ],\n    \"schema_version\": \"1.0\",\n    \"model_id\": \"claude-3.7-sonnet\"\n  }\n}\n
"},{"location":"fep/c81b/#minimal-note","title":"Minimal Note","text":"
{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    {\n      \"lessersoul\": \"https://spec.lessersoul.ai/ns/agent-attribution/v1#\",\n      \"agentAttribution\": {\n        \"@id\": \"lessersoul:agentAttribution\",\n        \"@type\": \"@json\"\n      }\n    }\n  ],\n  \"id\": \"https://example.com/users/agent-alpha/statuses/124\",\n  \"type\": \"Note\",\n  \"attributedTo\": \"https://example.com/users/agent-alpha\",\n  \"content\": \"<p>I was asked to reply.</p>\",\n  \"agentAttribution\": {\n    \"trigger_type\": \"manual\"\n  }\n}\n
"},{"location":"fep/c81b/#relationship-to-other-standards","title":"Relationship to Other Standards","text":""},{"location":"fep/c81b/#fep-2677","title":"FEP-2677","text":"

FEP-2677 defines how to identify the Application Actor and distinguishes Application actors (triggered by internal application events) from Service actors (triggered by inbox activities or external events and behaving more like user-controlled accounts). Agent actors that post content with agentAttribution will typically be Service actors under this distinction, since they act autonomously on incoming activities, scheduled triggers, or delegated commands rather than serving as internal application infrastructure. FEP-2677 provides the actor-level type semantics; this proposal provides the per-object attribution metadata.

"},{"location":"fep/c81b/#fep-c390","title":"FEP-c390","text":"

FEP-c390 addresses identity proofs for ActivityPub actors. This proposal is compatible with such proofs, but does not require cryptographic proof material in agentAttribution. A future extension MAY define a proof-bearing variant.

"},{"location":"fep/c81b/#scim-agent-extension","title":"SCIM Agent Extension","text":"

SCIM agent-oriented schemas commonly model owners, entitlements, and agent capabilities. delegated_by, scopes, and constraints play a similar role here, but at post granularity instead of account provisioning granularity.

"},{"location":"fep/c81b/#gnap-delegation-semantics","title":"GNAP delegation semantics","text":"

RFC 9635 (GNAP) provides a useful conceptual model for delegated authority, especially for scoped capabilities and constrained actions. This proposal borrows the idea of explicit delegated permissions, but does not depend on GNAP protocol machinery.

"},{"location":"fep/c81b/#mastodon-attributiondomains-and-fediversecreator","title":"Mastodon attributionDomains and fediverse:creator","text":"

Existing fediverse attribution mechanisms demonstrate deployment precedent for publishing creator-related metadata on federated objects. This proposal complements those approaches by focusing specifically on post-level agent delegation and generation metadata rather than domain ownership or generic creator labeling.

"},{"location":"fep/c81b/#w3c-social-web-wg-timeline","title":"W3C Social Web WG timeline","text":"

Informative note: the proposed W3C Social Web Working Group charter published in November 2025 lists ActivityPub maintenance work with an expected completion target of Q3 2026. Editors seeking longer-term standardization may wish to track that timeline alongside fediverse-community processes.

"},{"location":"fep/c81b/#security-considerations","title":"Security Considerations","text":""},{"location":"fep/c81b/#references","title":"References","text":""},{"location":"fep/c81b/#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/c893/","title":"FEP-c893: DOAP","text":"Authors Status Date received Tracking issue Discussions Repository AvidSeeker avidseeker7@protonmail.com DRAFT 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.

"},{"location":"fep/c893/#example-doapjsonld-file","title":"Example 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":""},{"location":"fep/c893/#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/cb76/","title":"FEP-cb76: Content Addressed Vocabulary","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository a a@trwnh.com WITHDRAWN 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.

"},{"location":"fep/cb76/#example-1-specifying-a-new-extension-term","title":"Example 1: Specifying a new extension term","text":"

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.

"},{"location":"fep/cb76/#example-2-standardizing-an-fep-with-a-new-extension-term","title":"Example 2: Standardizing an FEP with a new extension term","text":"

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":""},{"location":"fep/cb76/#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/cd47/","title":"FEP-cd47: Federation-friendly Addressing and Deduplication Use-Cases","text":"Authors Status Date received Tracking issue Discussions Repository Bumblefudge bumblefudge@learningproof.xyz DRAFT 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:

  1. Actors are what ActivityPub calls Actors: fediverse \"accounts\", bots, etc.
  2. Behavior can here be limited to \"the Greater Activity Streams,\" meaning protocol-native activities wrapped in AS objects defined in AS, AP, or in a FEP with a @Context so as to be fully compatible with the protocol in a ffpr, still canonicalizable as RDF.
  3. Content can here be a catch-all for three different sub-types of data: the \"contents\" of an Activity with arbitrary/open-ended fields (like the user-generated content of a 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":"
  1. Abusive activity often evades or actor-based attribution and moderation; one common form of this on social media is copy-pasted allegations or rumors which bad actors deploy across throwaway accounts, bots, parrot-accounts, friends/accomplices, etc. For this reason, circulating content of an activity like a Note might need to be tracked independent of URI/location, server, and actor in a uniform way, rather than per-activity or per-property (the prevalence of copy-pasta might even be used as a health or authenticity metric for networks, although of course this should not be overapplied as it might return false positives for some of the funniest copypasta meta-memes of the Terminally Online)
  2. Hate-Speech and interpersonal Trust and safety issues- Relying on the moderation of a given server or service-provider benefits greatly from interoperable logs of moderation decisions; per-decision receipts or events might benefit from indirection or multi-addressing actors, behaviors, and/or content that moves or is deleted, as well as potentially cacheing it privately for later replay/re-assesment/etc.
  3. Being able to sort moderation by categories or topics enables selective reliance or inheritance of moderation (and re-moderation in categories where servers differ on policy or obligations)
  4. Some regulations require reporting and/or storing for a period of time toxic or offending content, even if the Actor is removed from the service or deletes their offending Behavior or Content-- a location-independent key/value store by content-address can sever the \"server's copy\" from the \"user's/published copy\" for these purposes.
  5. Inauthentic activity, whether it be \"synthetic users\" (AI impersonating users for whatever commercial or analytics-inflating purposes) or AI flotsam or deliberate and targetted impersonation, deliberately exploits weaknesses or good-faith in the actor system. Being able to attribute many actors or \"sybils\" to a common \"shadow actor\" (a sybil factory, a script, a coordination point of a bot army, etc.) often requires identifying the behavioral pattern or network traffic properties, even probabilistically and then mapping that back to actors that do not otherwise share location-addressing or domain provenance.
  6. \"Chain-Letter\" Behaviors and Content is a common pattern in end-to-end encrypted systems like WhatsApp, which has been grappling with the ethical, legal, and political/disinformation consecuences of \"chain-letter\" style viral communications for decades. To mitigate such messaging patterns without violating the privacy assumptions of users of E2EE systems, traffic is analyzed to mark outbox content identical to content in the same user's inbox so that future recipients see it marked as a \"forward\", and \"replication speed limits\" are imposed to prevent sensational disinformation or deliberately inauthentic replication moving faster than good information.
  7. Deduplication for thorough Copyright Takedowns: Some copyright regulations (if not in the letter of the regulation, at least in the best-practice or spirit of the law) put the burden of deduplication on a service provider, hoster, and/or aggregator of user-generated content, i.e., a copyright takedown does not apply to a specific URL or a specific file but to the copyrighted material (film, book, etc) that you are hosting and aggregating, across multiple users or new accounts a user might create, and even if others users download and re-uploads it in a slightly altered form. For this reason, many copyright-enforcement regimes today are highly centralized because they require both a mammoth data lake of all reported violations, and some kind of matching algorithm that deduplicates in media-specific and encoding-specific ways across slight edits, crops, tints, tinges, etc.
  8. Note: One way of doing this is a \"distance hash\" or \"locality-sensitive hashing\" to create hashes that index commonalities between inputs (patterns, segments in common, structures) or, in audio-visual media, \"perceptual hashing\" that tries to canonicalize the final visual output of a visual encoding rather than its underlying data structure, compression, codecs, etc. One open-source example of this is ISCC
"},{"location":"fep/cd47/#user-stories","title":"User Stories","text":"
  1. As a server offering data migration to users bringing over old data, I can ingest content-addressed versions of each all actor collections, behavior and content without having to know server-specific paths, id patterns, annotations, bucket-storage URL schemes, etc.
  2. I can also check imported datasets of behavior and/or content against content-addressed moderation records from the server that originally hosted them, assuming that server is still live and federated with me.
  3. I can also access a mirror or snapshot of that data, if the server is no longer online or disfederated from me.
  4. As a server (A) auditing another server (B)'s moderation track record, I can parse receipts or logs of moderation actions that refer to behaviors and content by addresses internal to Server B (not necessarily content-addressed!) and use those address to retrieve the behavior and/or content moderated, even if the actors, behaviors, and/or content in question have been deleted. (Note: this use case assumes Server B is authorized appropriately by Server A and assumes significant trust between servers.)
  5. As a server promising its users the \"right to be forgotten\" (or just honoring UX expectations of deletion), I can request receipts of deletion from the servers of followers and followers' followers' by content-address of the behavior or content being deleted. (See fediverse-ideas#55 on fediverse-ideas reference; may be more pertinent for attachments than activities per se)
"},{"location":"fep/cd47/#open-questions","title":"Open Questions","text":"
  1. Ephemeral, \"in-memory\" and/or service-internal objects (which the AP spec recommends to be excluded from the axiom that all Activity objects should have a unique and dereferenceable 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.
  2. Should the Sub-Resource Integrity hashes of @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?
  3. Do Actors, Behaviors, and/or Contents need to be RDF-canonicalized, or is JCS more appropriate for portability between JSON-LD native and JSON-only implementations? What translation corner-cases arise from assuming one or the other canonicalization?
  4. Thanks to @pukkamustard for sharing their IETF RFC draft for RDF/CBOR, explainer, and relevant prior art-- seems a well-designed and standardizable way forward, at least for Behaviors, particularly for implementations that already have RDF-canonicalization in the dependency graph and featureset.
  5. Similarly, how are attachments, links, images, videos, etc canonicalized for hashing in content-type-aware ways? How to deal with filesystems, bucket storage, etc? How are arbitrary files (uploads?) canonicalized?
  6. Is there a property for Actor objects to backlink to previous ids? How to deduplicate across migrations that change id property?
"},{"location":"fep/cd47/#references","title":"References","text":"

Normative

Informational

"},{"location":"fep/cd47/#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/d36d/","title":"FEP-d36d: Sharing Content Across Federated Forums","text":"Authors Status Date received Tracking issue Discussions Repository Zack Dunn zack@tilde.team DRAFT 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.

"},{"location":"fep/d36d/#history","title":"History","text":"

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.

"},{"location":"fep/d36d/#activity-handling","title":"Activity Handling","text":"

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.net FINAL 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:

"},{"location":"fep/d556/#discovery","title":"Discovery","text":"

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:

"},{"location":"fep/d556/#mastodon-example","title":"Mastodon Example","text":"

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:

Since no user-related actor link is provided for the server resource, the self rel value can be used without ambiguity

"},{"location":"fep/d556/#related-proposals","title":"Related Proposals","text":"

FEP-2677 suggests using NodeInfo for a similar purpose. There are several disadvantages of this compared to using WebFinger.

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":""},{"location":"fep/d556/#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/d767/","title":"FEP-d767: Extend ActivityPub with Valueflows","text":"Authors Status Date received Date withdrawn Tracking issue Discussions Repository Lynn Foster lynnfoster@social.coop WITHDRAWN 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":""},{"location":"fep/d767/#references","title":"References","text":""},{"location":"fep/d767/#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/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.org DRAFT 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:

"},{"location":"fep/d8c2/#discovery","title":"Discovery","text":"

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.

"},{"location":"fep/d8c2/#authorization-server-metadata","title":"Authorization Server Metadata","text":"

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.

"},{"location":"fep/d8c2/#context-document","title":"Context document","text":"

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.

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.

"},{"location":"fep/d8c2/#objectidasclientid","title":"objectIDAsClientID","text":"

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.

"},{"location":"fep/d8c2/#examples","title":"Examples","text":""},{"location":"fep/d8c2/#actor-flag","title":"Actor flag","text":"

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.

"},{"location":"fep/d8c2/#security-considerations","title":"Security considerations","text":""},{"location":"fep/d8c2/#iana-considerations","title":"IANA Considerations","text":""},{"location":"fep/d8c2/#oauth-authorization-server-metadata-registry","title":"OAuth Authorization Server Metadata Registry","text":"

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.

"},{"location":"fep/d8c2/#references","title":"References","text":""},{"location":"fep/d8c2/#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/d8c8/","title":"FEP-d8c8: BitTorrent Torrent 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.

"},{"location":"fep/d8c8/#background","title":"Background","text":"

A torrent file1 is an abbreviated, verifiable description of a file or directory that, at minimum, contains

A 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.

"},{"location":"fep/d8c8/#spec","title":"Spec","text":"

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.

"},{"location":"fep/d8c8/#json-encoding","title":"JSON Encoding","text":"

Torrent objects MUST be JSON encoded, mapping bencoded strings, integers, lists, and dictionaries to their counterparts in JSON.

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

"},{"location":"fep/d8c8/#bencoding","title":"Bencoding","text":"

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.

"},{"location":"fep/d8c8/#additional-keys","title":"Additional Keys","text":"

Torrent objects MUST contain their infohashes using the following keys, depending on whether they are v1, v2, or hybrid torrents:

Software 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:

"},{"location":"fep/d8c8/#abbreviated-representation","title":"Abbreviated Representation","text":"

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.

"},{"location":"fep/d8c8/#examples","title":"Examples","text":""},{"location":"fep/d8c8/#torrent-objects","title":"Torrent Objects","text":"

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):

"},{"location":"fep/d8c8/#v1","title":"v1","text":"Expand/collapse v1 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\": 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)

"},{"location":"fep/d8c8/#discussion","title":"Discussion","text":""},{"location":"fep/d8c8/#string-encoding","title":"String Encoding","text":"

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:

"},{"location":"fep/d8c8/#references","title":"References","text":""},{"location":"fep/d8c8/#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/d8c8/#footnotes","title":"Footnotes","text":"
  1. A .torrent file is often just called \"a torrent,\" as it is in this document.\u00a0\u21a9

  2. 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

"},{"location":"fep/d8c8/implementation/","title":"FEP d8c8 Reference Implementation","text":""},{"location":"fep/d8c8/implementation/#usage","title":"Usage","text":"

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,

"},{"location":"fep/d8c8/implementation/#see-also","title":"See Also","text":"

torrent-models - a more complete implementation of .torrent files in python

"},{"location":"fep/d9ad/","title":"FEP-d9ad: Create Conformance Tests for Fediverse Enhancement Proposals","text":"Authors Status Date received Tracking issue Discussions Repository bengo ben@bengo.co 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":""},{"location":"fep/d9ad/#context","title":"Context","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:

"},{"location":"fep/d9ad/#proposal","title":"Proposal","text":"

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

"},{"location":"fep/d9ad/#conformance-test-components","title":"Conformance Test Components","text":"

Each of these components may be a part of a conformance test.

"},{"location":"fep/d9ad/#applicability","title":"Applicability","text":"

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

"},{"location":"fep/d9ad/#example","title":"Example","text":"

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.

"},{"location":"fep/d9ad/#uri","title":"URI","text":"

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

"},{"location":"fep/d9ad/#example_1","title":"Example","text":"

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

"},{"location":"fep/d9ad/#uri_3","title":"URI","text":"

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

"},{"location":"fep/d9ad/#identifier","title":"Identifier","text":"

a globally unique identifier that identifies the Conformance Test

The Identifier MUST be a URI.

Inspired by

"},{"location":"fep/d9ad/#examples","title":"Examples","text":""},{"location":"fep/d9ad/#uri_4","title":"URI","text":"

https://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:

The 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

"},{"location":"fep/d9ad/#uri_6","title":"URI","text":"

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

"},{"location":"fep/d9ad/#example-name","title":"Example Name","text":"

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

"},{"location":"fep/d9ad/#uri_9","title":"URI","text":"

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

"},{"location":"fep/d9ad/#uri_10","title":"URI","text":"

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

"},{"location":"fep/d9ad/#uri_11","title":"URI","text":"

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

"},{"location":"fep/d9ad/#test-suite-memberships","title":"Test Suite Memberships","text":"

The test suites that the test is a part of, if any.

For each test suite membership, the test should specify:

"},{"location":"fep/d9ad/#uri_12","title":"URI","text":"

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

"},{"location":"fep/d9ad/#appendix-conformance","title":"Appendix: Conformance","text":""},{"location":"fep/d9ad/#what-is-conformance","title":"What is Conformance?","text":"

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":"FEP-db0e: Authentication mechanism for non-public groups","text":"Authors Status Date received Tracking issue Discussions Repository Gregory Klyushnikov activitypub@grishka.me DRAFT 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.

"},{"location":"fep/db0e/#using-actor-tokens","title":"Using actor tokens","text":"

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:

"},{"location":"fep/db0e/#the-smactortoken-endpoint","title":"The sm: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.

"},{"location":"fep/db0e/#making-use-of-the-actor-token","title":"Making use of the actor token","text":"

To use an actor token when fetching an object, pass it as Authorization: ActivityPubActorToken {...} HTTP header.

"},{"location":"fep/db0e/#generation-of-the-source-string-for-signature","title":"Generation of the source string for signature","text":"
  1. Iterate over the keys in the actor token JSON object, skipping signature, and transform them into the format key: value. Add these strings to an array.
  2. Sort the resulting array lexicographically.
  3. Join the strings with newline character (\\n, U+000A).
  4. Convert the resulting string to a UTF-8 byte array.
"},{"location":"fep/db0e/#generation-of-the-actor-token","title":"Generation of the actor token","text":"
  1. Verify that the requesting actor, as per HTTP signature, has access to the group (there are members with the same domain). If it does not, you MUST return a 403 error and stop.
  2. Create a JSON object with the fields above (except signature). It is RECOMMENDED that the validity period is 30 minutes, and it MUST NOT exceed 2 hours.
  3. Generate a signature source string as above, sign it, and wrap the signature into an object with signature, algorithm, and keyId fields.
  4. Add the object as a single element in the signatures array.
  5. Return the resulting JSON object to the client.
"},{"location":"fep/db0e/#verification-of-the-actor-token","title":"Verification of the actor token","text":"
  1. Check that the HTTP signature is valid, and that actor in the token object matches the actor ID from keyId in the HTTP signature. Otherwise, you MUST return a 403 and stop.
  2. In the 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.
  3. Check the validity time: 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.
  4. Generate the signature source string as above and verify the signature.
  5. Check that the object the requester is accessing is, in fact, part of a collection owned by issuer.
  6. If all of the above checks pass, return the requested object. Otherwise, return a 403.
"},{"location":"fep/db0e/#example-of-an-actor-token-object","title":"Example of an actor token object","text":"
{\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":""},{"location":"fep/db0e/#references","title":"References","text":""},{"location":"fep/db0e/#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/db70/","title":"FEP-db70: RemoveAll Collection Activity","text":"Authors Status Date received Tracking issue Discussions Repository Fred Hauschel @naturzukunft2026@mastodon.social DRAFT 2026-03-15 #784 Discussions codeberg"},{"location":"fep/db70/#summary","title":"Summary","text":"

This FEP defines a RemoveAll activity for batch-removing items from an ActivityPub collection. While the ActivityPub specification defines Remove for removing items from a collection, it requires the client to know the identity of every item to remove. RemoveAll fills this gap. It supports an optional FEP-34c1 filter to selectively remove items matching specific criteria (e.g. by type, by date, or by actor). Without a filter, all items are removed. RemoveAll is a generic collection operation \u2014 it can be used with any collection type, not just inboxes or notification collections.

"},{"location":"fep/db70/#motivation","title":"Motivation","text":"

ActivityPub's Remove activity requires the client to know the identity of every object to remove from a target collection. For collections that can grow large, clients need a way to batch-remove items without sending individual Remove activities for each item. Common use cases include:

Without a batch operation, a client must first fetch all items, then send individual Remove activities \u2014 which is both slow and prone to race conditions.

"},{"location":"fep/db70/#specification","title":"Specification","text":""},{"location":"fep/db70/#1-the-removeall-activity","title":"1. The RemoveAll Activity","text":"

RemoveAll is a new activity type that removes multiple items from a target collection.

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/db70\"\n  ],\n  \"type\": \"RemoveAll\",\n  \"actor\": \"https://example.com/actors/bob\",\n  \"target\": \"https://example.com/actors/bob/notifications\"\n}\n
"},{"location":"fep/db70/#properties","title":"Properties","text":"Property Type Required Description type String MUST RemoveAll actor IRI MUST The actor performing the operation target IRI MUST The collection to remove items from filter FilterRequest MAY A FEP-34c1 filter to select which items to remove

Without a filter, the server MUST remove all items from the target collection.

"},{"location":"fep/db70/#2-filtered-removeall-with-fep-34c1","title":"2. Filtered RemoveAll (with FEP-34c1)","text":"

When a filter property is present, only items matching the filter are removed. The filter uses the FEP-34c1 FilterRequest format with TREE relations.

Remove all items matching a specific property value:

{\n  \"@context\": [\n    \"https://www.w3.org/ns/activitystreams\",\n    \"https://w3id.org/fep/db70\",\n    \"https://w3id.org/fep/34c1\",\n    \"https://w3id.org/tree\"\n  ],\n  \"type\": \"RemoveAll\",\n  \"actor\": \"https://example.com/actors/bob\",\n  \"target\": \"https://example.com/actors/bob/some-collection\",\n  \"filter\": {\n    \"type\": \"FilterRequest\",\n    \"relation\": [\n      {\n        \"type\": \"EqualToRelation\",\n        \"path\": { \"@id\": \"rdf:type\" },\n        \"value\": { \"@id\": \"as:Create\" }\n      }\n    ]\n  }\n}\n

Remove all items older than a given date:

{\n  \"type\": \"RemoveAll\",\n  \"actor\": \"https://example.com/actors/bob\",\n  \"target\": \"https://example.com/actors/bob/some-collection\",\n  \"filter\": {\n    \"type\": \"FilterRequest\",\n    \"relation\": [\n      {\n        \"type\": \"LessThanRelation\",\n        \"path\": { \"@id\": \"as:published\" },\n        \"value\": { \"@value\": \"2026-02-17T00:00:00Z\", \"@type\": \"xsd:dateTime\" }\n      }\n    ]\n  }\n}\n

Combine multiple filter criteria (AND):

{\n  \"type\": \"RemoveAll\",\n  \"actor\": \"https://example.com/actors/bob\",\n  \"target\": \"https://example.com/actors/bob/some-collection\",\n  \"filter\": {\n    \"type\": \"FilterRequest\",\n    \"relation\": [\n      {\n        \"type\": \"EqualToRelation\",\n        \"path\": { \"@id\": \"rdf:type\" },\n        \"value\": { \"@id\": \"as:Create\" }\n      },\n      {\n        \"type\": \"LessThanRelation\",\n        \"path\": { \"@id\": \"as:published\" },\n        \"value\": { \"@value\": \"2026-02-17T00:00:00Z\", \"@type\": \"xsd:dateTime\" }\n      }\n    ]\n  }\n}\n

Multiple relation entries are combined with AND \u2014 all criteria must match for an item to be removed.

"},{"location":"fep/db70/#3-server-behavior","title":"3. Server Behavior","text":""},{"location":"fep/db70/#4-response","title":"4. Response","text":"

The server SHOULD respond with 200 OK and the number of removed items:

{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"summary\": \"Removed 12 items from the collection\"\n}\n
"},{"location":"fep/db70/#security-considerations","title":"Security Considerations","text":""},{"location":"fep/db70/#conformance","title":"Conformance","text":"

A conforming server MUST: - Process RemoveAll activities targeting a collection owned by the actor - Remove all items when no filter is present - Remove only matching items when a FEP-34c1 filter is present

A conforming server SHOULD: - Support FEP-34c1 FilterRequest in the filter property

"},{"location":"fep/db70/#implementations","title":"Implementations","text":""},{"location":"fep/db70/#references","title":"References","text":""},{"location":"fep/db70/#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/dc88/","title":"FEP-dc88: Formatting Mathematics","text":"Authors Status Date received Tracking issue Discussions Repository Calvin Lee pounce@integraldoma.in DRAFT 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.

"},{"location":"fep/dc88/#sanitizing-mathematically-formatted-text","title":"Sanitizing Mathematically Formatted Text","text":"

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.

"},{"location":"fep/dc88/#examples","title":"Examples","text":"
{\"@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.

"},{"location":"fep/dc88/#semantic-attributes","title":"Semantic Attributes","text":"Element Attributes Values all 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":""},{"location":"fep/dc88/#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/dd4b/","title":"FEP-dd4b: Quote Posts","text":"Authors Status Date received Tracking issue Discussions Repository Evan Prodromou evan@socialwebfoundation.org DRAFT 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.

"},{"location":"fep/dd4b/#specification","title":"Specification","text":""},{"location":"fep/dd4b/#examples","title":"Examples","text":""},{"location":"fep/dd4b/#basic-quote-post","title":"Basic Quote Post","text":"

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.

"},{"location":"fep/dd4b/#security-considerations","title":"Security considerations","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.

"},{"location":"fep/dd4b/#privacy-considerations","title":"Privacy considerations","text":""},{"location":"fep/dd4b/#consent-to-be-shared","title":"Consent to be shared","text":"

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":""},{"location":"fep/dd4b/#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/e229/","title":"FEP-e229: Best practices for extensibility","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 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\"].

"},{"location":"fep/e229/#ignore-json-ld-context-if-you-dont-understand-it","title":"Ignore JSON-LD context if you don't understand it","text":"

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.

"},{"location":"fep/e229/#ld-aware-consumers","title":"LD-aware consumers","text":""},{"location":"fep/e229/#assume-the-as2-context-if-none-is-provided","title":"Assume the AS2 context if none is provided","text":"

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.

"},{"location":"fep/e229/#ld-unaware-producers","title":"LD-unaware producers","text":""},{"location":"fep/e229/#declare-iris-for-terms-that-are-expected-to-be-shared","title":"Declare IRIs for terms that are expected to be shared","text":"

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.

"},{"location":"fep/e229/#ld-aware-producers","title":"LD-aware producers","text":""},{"location":"fep/e229/#avoid-unnecessary-term-prefixes","title":"Avoid unnecessary term prefixes","text":"

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\".

"},{"location":"fep/e229/#consider-producing-documents-compacted-against-only-the-as2-context-document","title":"Consider producing documents compacted against only the AS2 context document","text":"

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:

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":""},{"location":"fep/e229/#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/e232/","title":"FEP-e232: Object Links","text":"Authors Status Date received Date final Tracking issue Discussions Repository silverpill @silverpill@mitra.social FINAL 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.

"},{"location":"fep/e232/#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/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:

"},{"location":"fep/e232/#examples","title":"Examples","text":"

(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.

"},{"location":"fep/e232/#implementations","title":"Implementations","text":""},{"location":"fep/e232/#references","title":"References","text":""},{"location":"fep/e232/#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/e3e9/","title":"FEP-e3e9: Actor-Relative URLs","text":"Authors Status Date received Tracking issue Discussions Repository Dmitri Zagidulin @dmitri@social.coop, bumblefudge bumblefudge@learningproof.xyz DRAFT 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:

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:

In addition:

"},{"location":"fep/e3e9/#validating-an-objects-authorcontroller","title":"Validating an Object's Author/Controller","text":"

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:

  1. The Client performs an HTTP GET request on the Object or Collection, as usual, including any currently required authorization headers.
  2. The client performing the 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.
  3. The Client follows the redirect and automatically fetches the object specified in the Location header of the 302 response (this behavior is the default in most HTTP clients).
  4. The Client extracts the current URL of the Object. This is the URL specified in the 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.
  5. The Client retrieves the Actor profile corresponding to this Object's author/ controller (the actor or attributedTo property).
  6. 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.

  7. 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.

"},{"location":"fep/e3e9/#client-side-implementation","title":"Client-Side Implementation","text":"

An ActivityPub client conforming to this FEP:

  1. When encountering an Actor-Relative URL as an ID of an object, fetch it using the same HTTP GET mechanism that it currently does.
  2. Note: An Actor-Relative URL is defined as a URL containing the service and relativeRef query parameters.
  3. The client MUST follow the 302 redirect in the response.
  4. The client MUST perform the validation steps outlined in the Validating an Object's Author/Controller section above.
"},{"location":"fep/e3e9/#server-side-implementation","title":"Server-Side Implementation","text":"

On the server side (specifically, the server hosting the Actor profile), an ActivityPub server conforming to this FEP:

  1. For every request to the Actor profile object (for example, to 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).
  2. 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:

  3. If the Actor profile does not contain a top level service property: INVALID

  4. If the Actor has a service property, but its value is null or []: INVALID
  5. Search 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

  6. 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).

  7. 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.

"},{"location":"fep/e3e9/#example-server-side-request-and-response","title":"Example Server-Side Request and Response","text":"

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:

  1. The direct URL (also called current location URL), https://old-storage-provider.example/AP/objects/567
  2. The indirect Actor-Relative URL https://alice-personal-site.example/actor?service=storage&relativeRef=/AP/objects/567

When 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.

"},{"location":"fep/e3e9/#references","title":"References","text":""},{"location":"fep/e3e9/#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/e965/","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.coop DRAFT 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.

"},{"location":"fep/e965/#current-approaches","title":"Current Approaches","text":"

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).

"},{"location":"fep/e965/#specification","title":"Specification","text":""},{"location":"fep/e965/#conformance","title":"Conformance","text":"

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:

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.

"},{"location":"fep/e965/#announcing-a-migration-or-deactivation-event","title":"Announcing a Migration or Deactivation Event","text":"

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:

"},{"location":"fep/e965/#behavior-for-unfamiliar-actor-uris","title":"Behavior for unfamiliar Actor URIs","text":"

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:

"},{"location":"fep/e965/#interpreting-a-move-activity-or-an-announce-activity-of-a-deactivated-actor","title":"Interpreting a Move Activity or an Announce Activity of a Deactivated Actor","text":"

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.

"},{"location":"fep/e965/#open-issues","title":"Open Issues","text":"
  1. Are there others to which an Actor-update Move or Announce should be addressed beyond just the old Actor's Followers, somehow imported? is it worth calling out server-instance Actors, since they might also want to know for... idunno moderation reasons?
  2. Announce Activity example
  3. Address Actor Equivalence Attestation objects explicitly, or leave up to implementer imagination?
"},{"location":"fep/e965/#references","title":"References","text":""},{"location":"fep/e965/#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/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:

  1. deactivation, OR
  2. migration to another URI, OR
  3. duplication at another URI.
"},{"location":"fep/e965/fep-e965-test-case/#about-this-test","title":"About this Test","text":"

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:

"},{"location":"fep/e965/fep-e965-test-case/#applicability","title":"Applicability","text":"

This test applies directly to the actor input.

"},{"location":"fep/e965/fep-e965-test-case/#test-targets","title":"Test Targets","text":""},{"location":"fep/e965/fep-e965-test-case/#expectations","title":"Expectations","text":"
  1. movedTo - MUST be a URI OR an empty string, if present
  2. copiedTo - MUST be a URI, if present
  3. movedTo and copiedTo MUST NOT both be present
  4. actor JSON's @context array SHOULD include \"https://w3id.org/fep/7628\" to signal conformance
"},{"location":"fep/e965/fep-e965-test-case/#assumptions","title":"Assumptions","text":""},{"location":"fep/e965/fep-e965-test-case/#1-how-to-determine-whether-an-actor-object-supports-fep-7628-semantics","title":"1. How to Determine Whether an Actor object supports FEP-7628 Semantics","text":"

For the purposes of determining whether the active status and migration history of a given Actor can be tested by this test case:

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:

"},{"location":"fep/e965/fep-e965-test-case/#property-value-evaluation-logic-pseudocode","title":"Property Value evaluation logic (Pseudocode)","text":""},{"location":"fep/e965/fep-e965-test-case/#test-cases","title":"Test Cases","text":"

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/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

"},{"location":"fep/e965/fep-e965-test-case/#both-movedto-and-copiedto-present","title":"both movedTo 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

"},{"location":"fep/e965/fep-e965-test-case/#movedto-set-to-array","title":"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

"},{"location":"fep/e965/fep-e965-test-case/#copiedto-contains-invalid-uri","title":"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

"},{"location":"fep/e965/fep-e965-test-case/#movedto-set-to-invalid-uri-1","title":"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

"},{"location":"fep/e965/fep-e965-test-case/#movedto-set-to-invalid-uri-2","title":"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

"},{"location":"fep/e965/fep-e965-test-case/#valid-deactivated-actor","title":"Valid Deactivated Actor","text":"

input

{\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

"},{"location":"fep/e965/fep-e965-test-case/#valid-migrated-actor","title":"Valid Migrated Actor","text":"

input

{\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

"},{"location":"fep/e965/fep-e965-test-case/#valid-migrated-actor-missing-tombstone","title":"Valid Migrated Actor (Missing Tombstone)","text":"

This 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

{\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

"},{"location":"fep/e965/fep-e965-test-case/#valid-multi-homed-actor","title":"Valid Multi-homed Actor","text":"

input

{\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

"},{"location":"fep/e965/fep-e965-test-case/#valid-multi-homed-actor-copiedto-set-to-array","title":"Valid Multi-homed Actor (copiedTo 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

"},{"location":"fep/e965/fep-e965-test-case/#glossary","title":"Glossary","text":""},{"location":"fep/e965/fep-e965-test-case/#outcome","title":"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:

"},{"location":"fep/e965/fep-e965-test-case/#requirements-mapping","title":"Requirements Mapping","text":""},{"location":"fep/e965/fep-e965-test-case/#references","title":"References","text":""},{"location":"fep/e965/fep-e965-test-case/#change-log","title":"Change Log","text":""},{"location":"fep/e965/fep-e965-test-case/#issues-list","title":"Issues List","text":""},{"location":"fep/eb22/","title":"FEP-eb22: Supported ActivityStreams types with NodeInfo","text":"Authors Status Date received Tracking issue Discussions Repository Manton Reece manton@micro.blog DRAFT 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:

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.

"},{"location":"fep/eb22/#supporting-common-social-features-and-book-reviews-review","title":"Supporting common social features and book reviews (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:

"},{"location":"fep/eb22/#references","title":"References","text":""},{"location":"fep/eb22/#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/eb48/","title":"FEP-eb48: Hashtags","text":"Authors Status Date received Tracking issue Discussions Repository AvidSeeker avidseeker7@protonmail.com DRAFT 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:

  1. It starts with the # symbol.
  2. It is followed by one or more alphanumeric characters (letters A-Z, a-z, or digits 0-9).
  3. It may include underscores (_) but must not include any other special characters, spaces, or punctuation within the hashtag itself.
"},{"location":"fep/eb48/#examples","title":"Examples","text":"

The following examples illustrate how hashtags should be identified and highlighted:

"},{"location":"fep/eb48/#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/ee3a/","title":"FEP-ee3a: Exif metadata support","text":"Authors Status Date received Tracking issue Discussions Repository Marcin Czachurski mczachurski@icloud.com DRAFT 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.

"},{"location":"fep/ee3a/#motivation","title":"Motivation","text":"

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.

"},{"location":"fep/ee3a/#privacy-considerations","title":"Privacy considerations","text":"

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.

"},{"location":"fep/ee3a/#property-names","title":"Property names","text":"

Property names MUST be strings corresponding to the Exif specification Field name (e.g., \"FNumber\", \"PhotographicSensitivity\", \"FocalLength\").

"},{"location":"fep/ee3a/#recommended-fields","title":"Recommended fields","text":"

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.

Name (PropertyValue.name) Applies to Description 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/#implementations","title":"Implementations","text":""},{"location":"fep/ee3a/#servers","title":"Servers","text":"

This list is not comprehensive:

"},{"location":"fep/ee3a/#references","title":"References","text":""},{"location":"fep/ee3a/#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/ef61/","title":"FEP-ef61: Portable Objects","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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:

"},{"location":"fep/ef61/#history","title":"History","text":"

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

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.

"},{"location":"fep/ef61/#comparing-ap-uris","title":"Comparing 'ap' URIs","text":"

Two 'ap' URIs are equivalent when their canonical forms are identical.

To produce a canonical 'ap' URI, the following operations MUST be performed:

"},{"location":"fep/ef61/#did-methods","title":"DID methods","text":"

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/#portable-objects","title":"Portable objects","text":"

Example of a portable object:

{\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/#retrieving-objects","title":"Retrieving objects","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 an object is not public, the server MUST NOT serve it unless the request is signed by an actor who belongs to object's intended audience.

When working with portable objects, the server SHOULD treat 'ap' URIs as opaque identifiers (semantic routing).

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:

Important

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 identified by 'ap' URIs (including inbox and outbox collections) MAY be served without FEP-8b32 integrity proofs. Consuming implementations MUST NOT process unsecured collections attributed to a portable actor if they were retrieved from a server that is not listed in the gateways array of the actor document.

Portable collections can be filtered and paginated in a same way as non-portable collections. A gateway MUST remove the integrity proof when generating a view of a collection created by a FEP-ae97 client.

"},{"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.

"},{"location":"fep/ef61/#webfinger-addresses","title":"WebFinger addresses","text":"

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.

"},{"location":"fep/ef61/#discussion","title":"Discussion","text":"

(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:

"},{"location":"fep/ef61/#did-services","title":"DID services","text":"

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.

"},{"location":"fep/ef61/#media-access-control","title":"Media access control","text":"

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.

"},{"location":"fep/ef61/#compatibility_1","title":"Compatibility","text":"

The following alternatives to gateway-based compatible IDs are being considered:

  1. Use regular HTTP(S) URIs but specify the canonical 'ap' URI using the 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.
  2. Alter object ID depending on the capabilities of the peer (which can be reported by NodeInfo or some other mechanism).
"},{"location":"fep/ef61/#implementations","title":"Implementations","text":""},{"location":"fep/ef61/#references","title":"References","text":""},{"location":"fep/ef61/#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/efda/","title":"FEP-efda: Followable objects","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 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:

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.

"},{"location":"fep/efda/#requirements","title":"Requirements","text":"

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:

A proposed modified requirement and algorithm are as follows:

"},{"location":"fep/efda/#algorithm","title":"Algorithm","text":"

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":""},{"location":"fep/efda/#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/f011/","title":"FEP-f011: Full-Text Search Query Syntax for ActivityPub","text":"Authors Status Type Date received Tracking issue Discussions Repository Steve Bate svc-fep@stevebate.net DRAFT implementation 2026-03-17 #791 Discussions codeberg"},{"location":"fep/f011/#summary","title":"Summary","text":"

This proposal defines a full-text search syntax for ActivityPub client support.

The syntax supports:

"},{"location":"fep/f011/#terminology","title":"Terminology","text":"

The term query string means the value of the q query parameter in an HTTP request against a search endpoint.

The term faceted expression means <facet>:expr, where expr may be a term, phrase, regex, range, or parenthesized subexpression and the facet is typically a JSON property path.

The term unfaceted expression means a term, phrase, regex, or group that does not have a facet prefix.

"},{"location":"fep/f011/#query-syntax","title":"Query Syntax","text":""},{"location":"fep/f011/#grammar","title":"Grammar","text":"

The (simplified) grammar proposed by this FEP is:

query      := or_expr\nor_expr    := and_expr (\"OR\" and_expr)*\nand_expr   := unary ((\"AND\" unary) | (implicit_and unary))*\nunary      := \"NOT\" unary | primary\nprimary    := term | phrase | regex | range | field_expr | \"(\" query \")\"\nfield_expr := WORD \":\" (primary | \"(\" query \")\")\nrange      := (\"[\" | \"{\") bound TO bound (\"]\" | \"}\")\nbound      := WORD | phrase\nphrase     := '\"' <any-char-except-quote>* '\"'\nregex      := \"/\" <regex-body> \"/\"\nterm       := WORD\n

An AND is implicit when two primaries are adjacent. The \"regex-body\" SHOULD conform to RFC9485 (Interoperable Regular Expression Format).

A full ABNF grammar is available. This can be used with parser generators like ANTLR.

"},{"location":"fep/f011/#token-rules","title":"Token Rules","text":""},{"location":"fep/f011/#operator-precedence","title":"Operator Precedence","text":"

The parser evaluates operators in this order:

  1. Parenthesized expressions
  2. Unary NOT
  3. AND (explicit and implicit)
  4. OR

AND and OR are left-associative.

"},{"location":"fep/f011/#faceted-expressions","title":"Faceted Expressions","text":"

A fielded expression has this form:

<facet>:value\n<facet>:\"some phrase\"\n<facet>:/xy.*/\n<facet>:[10 TO 20]\n<facet>:(cats OR dogs)\n

<facet>:(...) applies the nested expression to the facet value.

"},{"location":"fep/f011/#range-expressions","title":"Range Expressions","text":"

Ranges support inclusive and exclusive delimiters independently:

Unbounded bounds may use * where supported by the evaluator implementation (e.g., rank:[4 TO *]).

"},{"location":"fep/f011/#search-endpoint-discovery","title":"Search Endpoint Discovery","text":"

The search endpoint is specified using the search property of the ActivityPub actor endpoints object. For example:

{\n  \"@context\": [\n    \"https://w3id.org/fep/f011\"\n    \"https://www.w3.org/ns/activitystreams\"\n  ],\n  ...\n  \"endpoints\": {\n    \"search\": \"https://server.example/search\"\n  }\n}\n
"},{"location":"fep/f011/#http-usage","title":"HTTP Usage","text":"

This syntax is used as the q parameter on the search endpoint.

GET https://server.example/search?q=<query>\n
"},{"location":"fep/f011/#responses","title":"Responses","text":"

Responses MUST be an ActivityPub Collection or OrderedCollection. Paging is optional. If an OrderedCollection is returned, the results SHOULD be considered ranked although the ranking criteria will not be known by the client ( possibilities are relevance/BM25, recency, etc.).

"},{"location":"fep/f011/#example-1-simple-primary-term","title":"Example 1: Simple Primary Term","text":"

Request: cats

GET /search?q=cats HTTP/1.1\nHost: social.example\nAccept: application/activity+json\n
"},{"location":"fep/f011/#example-2-faceted-unfaceted-phrase","title":"Example 2: Faceted + Unfaceted phrase","text":"

Request: tag:fediverse language:en \"ActivityPub client search\"

GET /search?q=tag%3Afediverse%20language%3Aen%20%22ActivityPub%20client%20search%22 HTTP/1.1\nHost: social.example\nAccept: application/activity+json\n

Response:

{\n  \"@context\": \"https://www.w3.org/ns/activitystreams\",\n  \"type\": \"OrderedCollection\",\n  \"id\": \"https://social.example/search?q=tag:fediverse language:en \\\"ActivityPub client search\\\"\",\n  \"totalItems\": 1,\n  \"orderedItems\": [\n    {\n      \"id\": \"https://social.example/notes/1\",\n      \"type\": \"Note\"\n    }\n  ]\n}\n
"},{"location":"fep/f011/#example-3-boolean-grouping","title":"Example 3: Boolean + Grouping","text":"

Request:v(cats OR dogs) AND NOT testing

GET /search?q=%28cats%20OR%20dogs%29%20AND%20NOT%20testing HTTP/1.1\nHost: social.example\nAccept: application/activity+json\n
"},{"location":"fep/f011/#example-4-regex","title":"Example 4: Regex","text":"

Request: type:Person preferredUsername:/alic.*/

GET /search?q=type%3APerson%20preferredUsername%3A%2Ffedi.%2A%2F\nHost: social.example\nAccept: application/activity+json\n

This could be useful for auto completion. Using a type:HashTag search term could similarly be used for hashtag autocompletion.

"},{"location":"fep/f011/#example-5-range","title":"Example 5: Range","text":"

Request: rank:[2 TO 4]

GET /search?q=rank%3A%5B2%20TO%204%5D HTTP/1.1\nHost: social.example\nAccept: application/activity+json\n
"},{"location":"fep/f011/#error-handling","title":"Error Handling","text":"

Invalid syntax MUST result in an HTTP client error status. Examples include:

Implementations MAY include details in a problem response body.

"},{"location":"fep/f011/#interoperability-notes","title":"Interoperability Notes","text":""},{"location":"fep/f011/#implementations","title":"Implementations","text":""},{"location":"fep/f011/#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/f06f/","title":"FEP-f06f: Object observers","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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.

"},{"location":"fep/f06f/#non-forwarding-observers","title":"Non-forwarding observers","text":"

If forwarding is not desirable, object observers can use Announce activity to distribute observed activities.

"},{"location":"fep/f06f/#examples","title":"Examples","text":"

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":""},{"location":"fep/f06f/#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/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.nz DRAFT 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.

"},{"location":"fep/f15d/#referencing-threaded-objects-as-a-whole","title":"Referencing threaded objects as a whole","text":"

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:

"},{"location":"fep/f15d/#audience-wrapper","title":"Audience wrapper","text":"

Implementations MAY wrap the Move activity in an Announce activity. Receivers are recommended to support both types.

"},{"location":"fep/f15d/#removal","title":"Removal","text":"

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.

"},{"location":"fep/f15d/#moderator-collection-check","title":"Moderator Collection Check","text":"

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.

"},{"location":"fep/f15d/#implementors","title":"Implementors","text":""},{"location":"fep/f15d/#references","title":"References","text":""},{"location":"fep/f15d/#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/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.social FINAL 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:

"},{"location":"fep/f1d5/#implementations","title":"Implementations","text":""},{"location":"fep/f1d5/#servers","title":"Servers","text":"

This list is not comprehensive:

"},{"location":"fep/f1d5/#clients","title":"Clients","text":""},{"location":"fep/f1d5/#references","title":"References","text":""},{"location":"fep/f1d5/#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/f228/","title":"FEP-f228: Backfilling conversations","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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.

"},{"location":"fep/f228/#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/f228/#collection-of-posts","title":"Collection of posts","text":"

The items of this collection are attributed objects, such as Note or Article objects. It represents a thread, as seen 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:

It 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.

contextHistory property is used to make a reference from a top-level post to related collection of activities.

Note

The collection of conversation activities was originally a part of Conversation Containers proposal.

"},{"location":"fep/f228/#reading-collections","title":"Reading collections","text":"

After top-level post of a conversation is discovered, the whole conversation can be retrieved using the following algorithm:

"},{"location":"fep/f228/#implementations","title":"Implementations","text":"

Collection of posts:

Collection of activities:

"},{"location":"fep/f228/#references","title":"References","text":""},{"location":"fep/f228/#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/fb2a/","title":"FEP-fb2a: Actor metadata","text":"Authors Status Date received Tracking issue Discussions Repository a a@trwnh.com DRAFT 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).

"},{"location":"fep/fb2a/#1-using-activitystreams-note-instead-of-schemaorg-propertyvalue","title":"1. Using ActivityStreams Note instead of schema.org PropertyValue","text":"

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.

"},{"location":"fep/fb2a/#3-defining-generic-actor-metadata-as-links","title":"3. Defining generic actor metadata as links","text":"

Actor metadata fields may also take the form of a link rather than a content value.

"},{"location":"fep/fb2a/#4-backwards-compatibility-with-legacy-implementations-of-profile-fields-using-incorrect-schemaorg-iris","title":"4. Backwards compatibility with legacy implementations of profile fields using incorrect schema.org IRIs","text":"

(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:

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).

"},{"location":"fep/fb2a/#5-examples-of-current-legacy-implementations-compared-to-serialization-according-to-this-fep","title":"5. Examples of current legacy implementations compared to serialization according to this FEP","text":"

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":""},{"location":"fep/fb2a/#references","title":"References","text":""},{"location":"fep/fb2a/#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/fc48/","title":"FEP-fc48: Generic ActivityPub server","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT implementation 2026-02-27 #769 Discussions codeberg"},{"location":"fep/fc48/#summary","title":"Summary","text":"

Generic ActivityPub server is a server that implements standard ActivityPub client API or FEP-ae97 client API, and can process any activity (including activities those behavior is not defined in the ActivityPub specification).

"},{"location":"fep/fc48/#motivation","title":"Motivation","text":"

Most of existing ActivityPub servers are designed for a specific type of application: a micro-blog, a forum, a video sharing service. This leads to a situation where users are expected to have separate accounts for each application type.

Even a server that implements the entirety of ActivityPub specification is limited because it doesn't support custom activities.

A truly generic server does not have these limitations and can work with any type of client application.

"},{"location":"fep/fc48/#object-classification","title":"Object classification","text":"

A generic server MUST determine the class of an object before processing it. The classification of ActivityPub objects is covered in FEP-2277: ActivityPub core types.

"},{"location":"fep/fc48/#activities-and-side-effects","title":"Activities and side effects","text":"

A generic server MUST support the following basic activities:

The side effects of these activities are implicit and are defined in the ActivityPub specification.

A generic server can only carry out the side effects of basic activities. Therefore, clients MUST specify the side effects of all other activities as additional activities. Clients can embed them into an activity using the result property, or send them separately.

A generic server MAY support other activity types defined in the ActivityPub specification.

"},{"location":"fep/fc48/#authentication-and-authorization","title":"Authentication and authorization","text":"

A generic server MUST verify all objects according to the universal authentication and authorization procedures described in FEP-fe34: Origin-based security model.

When processing activities submitted by a client, the server can only check permissions for basic activities. Therefore, recipients SHOULD NOT use the same-origin policy to verify permissions.

"},{"location":"fep/fc48/#managing-collections","title":"Managing collections","text":"

A generic server MUST automatically create inbox, outbox and other actor collections defined in the ActivityPub specification after registering an actor.

Other collections MUST be created by clients using Create activities where object is an empty collection.

"},{"location":"fep/fc48/#references","title":"References","text":""},{"location":"fep/fc48/#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/fe34/","title":"FEP-fe34: Origin-based security model","text":"Authors Status Type Date received Tracking issue Discussions Repository silverpill @silverpill@mitra.social DRAFT 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 responsible for enforcing security boundaries between the hosted actors. This assumption is based on the general consensus that served objects can be trusted and that their ownership doesn't require verification. 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 the server enforces boundaries between them.

"},{"location":"fep/fe34/#comparing-origins","title":"Comparing origins","text":"
  1. Let uri-scheme be the scheme component of the URI, converted to lowercase.
  2. Let uri-host be the host component of the URI, converted to lowercase.
  3. If there is no port component of the URI, let uri-port be the default port for the protocol given by uri-scheme. Otherwise, let uri-port be the port component of the URI.
  4. Return the triple (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.

"},{"location":"fep/fe34/#embedding","title":"Embedding","text":"

In some cases, an embedded object can be trusted when its wrapping object is trusted:

Servers MUST NOT allow clients to publish activities where embedded objects are owned by another local actor.

Embedded non-anonymous objects SHOULD NOT be partial representations. A server that relies on embedding for authentication might save a partial representation of an object to the cache, replacing the full object.

Note

Partial representations could be marked to prevent caching. Potential methods for achieving this are discussed in the issue https://codeberg.org/silverpill/feps/issues/21.

"},{"location":"fep/fe34/#authorization","title":"Authorization","text":"

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:

In some cases ownership might be implicit. Examples:

Anonymous objects are not supposed to have an owner.

Applications can use the following algorithm to determine the owner of an object:

  1. Run the duck typing algorithm specified in FEP-2277.
  2. If the type is Link, return error.
  3. If the type is neither Object nor Collection, and the object has an attributedTo property, return error.
  4. If the type is Actor, return the value of the id property.
  5. If the type is VerificationMethod, return the value of the controller property.
  6. If the type is PublicKey, return the value of the owner property.
  7. If the type is Activity, return the value of the actor property.
  8. If the type is 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.

"},{"location":"fep/fe34/#comparing-owners","title":"Comparing owners","text":"

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:

Servers MUST ensure that activities published by a client do not represent unauthorized actions. This includes activities embedded within other activities and objects.

"},{"location":"fep/fe34/#access-control","title":"Access control","text":"

The intended audience of an object is specified using the following properties: to, cc, bto, bcc and audience.

An object addressed to https://www.w3.org/ns/activitystreams#Public can be retrieved by anyone. When an object is not addressed to public, the server MUST verify that the GET request contains an HTTP signature created using a key whose owner belongs to the 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.

Note

Some servers always require signed requests in an attempt to limit access to public objects. However, this measure is often ineffective and can be circumvented by using a different domain name to serve the public key.

"},{"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:

"},{"location":"fep/fe34/#cross-origin-relationships","title":"Cross-origin relationships","text":"

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:

"},{"location":"fep/fe34/#references","title":"References","text":""},{"location":"fep/fe34/#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/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.

"},{"location":"fep/fffd/#4-merging","title":"4. Merging","text":"

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.

"},{"location":"fep/fffd/#5-protocols","title":"5. Protocols","text":"

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.

"},{"location":"fep/fffd/#51-well-known-alternate-protocols","title":"5.1. Well-known Alternate Protocols","text":""},{"location":"fep/fffd/#52-well-known-media-types","title":"5.2. Well-known Media Types","text":"

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":""},{"location":"fep/fffd/#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.

"}]} \ No newline at end of file