How to represent a request for access to a shared resource

Alice wants to give Bob access to her project MyProject. So:

  1. Alice sends an Invite activity
  2. Bob sends an Accept activity
  3. MyProject sees these activities and gives Bob access

So there’s an Invite->Accept flow. Very simple, I hope.

Now suppose Cindy wants to request access to MyProject. How would I represent the request (and any activities following it) in ActivityPub? My current proposal:

  1. Cindy sends a Join activity
  2. Alice sends an Accept to approve the request
  3. MyProject sees these activities and gives Cindy access

Does this make sense to you, this flow of Join->Accept? Are there better activity types you’d use for this? I’d love feedback and to hear people’s thoughts before I put this into a spec :slight_smile: :pray:

(A downside of using Join is that it may look like Cindy is declaring she’s already joined, rather than requesting to join… Invite doesn’t have this problem, so another option is for Cindy to send an Invite where she’s inviting herself… but that’s a bit weird… what do you think?)


Join and Accept Join is fine, if everyone agrees that Join activities need to be Accepted. ActivityPub doesn’t actually define usage of the Join activity, so this is already in extension territory. It seems reasonable to say “a Join activity needs to be Accepted first”, just like a Follow.

If you really don’t like that for some reason, then there’s the Offer activity type, which if Accepted will cause the side effects to be processed by that actor… well, in theory, anyway. Offer is also not defined in ActivityPub. But you could theoretically do Offer Invite to ask for an invite… it would be a really roundabout way of doing things, though, so I recommend against it. Alternatively, you could do Offer Join if you really wanted to, i dunno, be really explicit about the request? I still recommend against it, though.

Since MyProject is the one who has to understand the semantics of the activities anyway, then why not just define it such that Join and Accept Join is the expected flow? For what it’s worth, this is how the groups feature is being planned in Mastodon, Smithereen, Pixelfed, etc – it is possible to Follow a Group actor, but Join is explicitly used to add membership semantics and access control within the group.

TL;DR: Just use Join and Accept Join, it’s the most straightforward way of doing things and the “downside” doesn’t really matter


In the Activity Vocabulary document, very little is said about Join, but it does state that Join “indicates that the actor has joined the object . The target and origin typically have no defined meaning”. That seems to imply that “Join” is envisioned as the activity announcing the completion of the joining process, not as the means to initiate it.

That said, what the Activity Vocabulary says about “Follow” is that it " indicates that the actor is ‘following’ the object. Following is defined in the sense typically used within Social systems in which the actor is interested in any activity performed by or on the object. The target and origin typically have no defined meaning."

As with Join, you wouldn’t guess from that wording that a “Follow” is how you start the process of following an actor, a process completed by “Accept” or “Reject”. But that is how Follow is treated in ActivityPub.

As long as you don’t conflict with usage that ActivityPub has made of the vocabulary, it would seem that you have latitude to use it as you wish.


I had a discussion in the Social Coding Foundations chatroom with @fr33domlover about this same subject.

What is a imho useful distinction to make in defining semantics is the consideration whether something is a Command (something I’d like to happen) or an Event (something that already happened. There’s no clarity in the specs and no established best-practices for modeling messaging patterns and vocab extensions.

And in app implementations people are freely choosing one or the other. This will make it harder and harder to establish broad interoperability between different app types when you can only deduce from the very specific context of a particular message exchange how to interpret the semantics.

Event Driven Architectures not surprisingly only deal with events, that are sent over a message bus and may trigger commands in any of the subscribers to the bus. In Domain Driven Design the commands are typically not part of the Domain model, but Domain Events are.

Going back to this use case for AP I’d have a preference that apps are sending events to each other. How would that look like in this case, and my interpretation?

  • Offer{Join}: “Bob offers to join the Project / Group” (an event: the offer stands)
  • Accept{Join}: “Alice accepts Bob’s offer to join” (event too: accepted)
  • Join{Person}: “Project / Group states that Bob joined as member” (yep, an event)

Now that last one goes a bit against the design you had in mind, presumably? And if the Project broadcasts anything at all after Bob’s membership, it might as well be a Note to inform the wider world, right?

But that would be thinking from the perspective of a Microblogging domain only, where Note means toot / notification. But the semantic meaning of what just happened to the Project / Group is lost for other application types. There’s less ability for other apps on the fediverse to meaningfully react to the event, unless they hardcode app-specific situations again (“This note is coming from a Project and has this and that properties, so it must be that someone joined”).

OTOH the Join{Person} event sent from the Project / Group can trigger much more targeted behavior elsewhere to any (independent) app subscribing to that event. Like e.g.:

  • “If Bob is member of ProjectXYZ then that makes Bob automatically a member of our Community”

  • “Bobs membership will allow Bob to have access to our Service”

  • “Another membership to the Projects of our Cooperative. Let’s update our Administration and Reports”

  • (Etcetera… by all means go crazy here in listing possibilities)

TL;DR: Offer{Join} should not be discarded too easily, and the plain Join / Accept flow might be simplest but you should realize that it makes a concession to the semantics and is mostly thought of as mechanism and natural extension that fits current Microblogging use cases primarily. It has drawbacks that may come to bite later on.

There’s a distinction to be made between AS2 and AP. AP is an extension to AS2 and uses the AS2 Vocabulary, but it defines its own specific semantics on top of that. One of those semantics is that “a Follow must first be Accepted”. If we are going to extend AP to include Join activities, then it makes sense to say that we should impose similar semantics, so “a Join must first be Accepted” as well.

There is also another finer point to be made, and that is that you are typically directly delivering these activities to the actor you intend to Follow/Join, which means that the actor is the one who decides how to process your activity. Imagine sending a Follow to someone. What happens next? In the absence of an Accept activity, you cannot assume that your Follow was successful. The actor simply will not send you any of its activities. It would be a similar case with Join, in which case the Group will simply not distribute any of its activities to you. So, in effect, the initial Follow / Join is meaningless and implies nothing without the Accept.

No? The Project is free to broadcast anything it wishes. It doesn’t have to be a Note, and in fact, a Note wouldn’t make sense.

A couple of things:

  • Join Person makes no semantic sense, it would have to be Join Group or Join Project or some other joinable type (likely defined by an extension property)
  • There is no guarantee that the Project or Group or joinable actor will be broadcasting the Join activity directly. In fact, it doesn’t really make sense to do so without the associated Accept. You could presumably extend or alter FEP-5624 to use a generic approval property instead of specifically replyApproval, and in that way the joining actor can advertise their Join along with its Accept linked within. But this is inefficient and limited in usefulness. You could publish such a Join to your outbox if you wished, but for the purposes of the Group/Project, who will be distributing its own activities under its own authority, the clearer path forward is to send out Add Person to the members Collection.

So Offer Join is a possible way to do things, but it clashes with the way things are already done, which are simpler:

  • Follow/Join
  • Accept Follow/Join
  • Add to relevant Collection
1 Like

You are likely right on these things when correlating them to what is currently used on the fedi. Though more than mentioning specific examples I was triggered by looking at the specs and the limited choices one has to express richer semantics (without going full custom). Plus observing there are multiple ways and only consensus to state which one is right to use. I don’t really see why Join Person does not make sense if the actor is the Project and I know to interpret the message as an event. It may not make sense in light of the current choices that are already made for federation?

re: Join Person, it doesn’t really make sense in plain English to me. What does it mean for a Project to Join a Person? A Person isn’t generally “joinable”. What makes far more sense is for the Project to Add the Person to the Collection of members.

1 Like

Ah, but events are best expressed with passed tense in plain English, and also actors are like ‘speaking entities’. If that message comes to me as some other actor I ‘hear’ ProjectXYZ saying “Bob has joined me”. The collection of members is something else that may be nice to have as an extension that allows iterating them or something.

It wouldn’t be “Bob has joined me”, it would be “I have joined Bob”, implying “I am a member of Bob”, which makes no sense. ActivityStreams uses actor-activity-object semantics.

1 Like

My understanding of “join” in English is that A joins B means something like A has become a member of B. The converse is not implied: A joins B does not imply that B joins A. If there is a collection of members of B, then A joins B implies that A has been added to the collection of members of B.

A Person object doesn’t seem semantically like something which can be “joined”. Indeed, of the various Actor objects, Person seems like the only one that cannot be joined, because a Person does not have “members”. Groups, Organizations, even Applications and Services, all seem like they could have “members”, and be joinable.

I am not sure why ActivityStreams even needs Join, since an Add to a members collection seems to amount to the same thing as a Join, and probably is the side-effect of a Join, anyway. Join seems like an alias of “Add”, really.

Follow is a little different. The actor is announcing that they are “following” in the social media sense another actor. This doesn’t require anybody’s approval. I can follow anybody I want by doing searches and reading their posts, and Follow lets me tell the world I am doing that. However, if the actor I am following sees the Follow and wants (or belongs to a server that auto-accepts), they can add me to their followers collection, which will result in my being notified of their posts. That is an “Accept”. (Why not an Add?) Or they can not do that. If they really want to be emphatic about it, they can not add me to their followers collection, and announce that they “Reject” the fact that I am following them (which seems a little unnecessary and surly.)

1 Like

Off topic but somewhat related - the activitystreams vocabulary supports Move but doesn’t have a concept called Copy. I’ve decided to support this anyway since there are times when you want to put something in a new location without being forced to delete it from the original location.

Anyways, the key difference between Join and Add is how the activity is structured. It can either be ‘Join/Group’ or ‘Add/Person {object}/Group {target}’. I don’t have a problem supporting both, because as long as the spec doesn’t forbid it, somebody is destined to do it that way.

1 Like

Wow. Thank you for this all this discussion and examining the various options.

FYI I’m going with the Join->Accept approach that I proposed, seems best.

Maybe someday we’ll have some spec/FEP to make it clear whether some activity is a request or a notification :stuck_out_tongue:

1 Like