I have written a draft FEP and would like comment before opening the pull request.
The situation. Client-to-server submission is described for an object that is born with its activity: a client posts a Note, the server wraps it in a Create, and both are given identifiers. Where federation is added to a content store that already exists, the order is reversed, and that is the normal case for a content management system. The store assigns an identifier, an author and an audience when the content is written; a plugin or module adds federation later; so an object can sit there for years before any actor decides to publish it. Publishing older content, publishing selectively, and publishing under one of several actors are then ordinary requests.
For those servers the client has nothing new to submit. It wants to name something the server already hosts:
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Create",
"actor": "https://example.org/actors/alice",
"object": "https://example.org/articles/123"
}
The syntax is not new — this is the shape of the Minimal Activity example, with both properties as URIs. What is missing is what an outbox should do with it. Section 6.2.1 is written throughout for an object the server is about to create, including the requirement to attach an identifier to the wrapped object, and says nothing about one that already has an identifier. w3c/activitypub#438 asks a neighbouring question and has been open since 2024. MediaUpload reached for the same shape in 2017 and has not been edited since.
What the draft says. Most of it follows from roles the specifications already define: do not give the referenced object a new identifier; take the activity’s addressing from the object rather than from the request; reject a request that carries its own to/cc/bto/bcc/audience rather than ignoring it, because ignoring it lets a client believe it published narrowly when it did not; refuse a reference the server cannot resolve rather than creating something to satisfy it; and check two permissions separately — permission to submit to that outbox, and permission for that actor to publish that object.
The part I think is new. Because such a submission carries no document, actor and object describe it completely, so a server can recognise a repeat as a retry. That is not generally possible in client-to-server: two submissions carrying identical documents may be two genuine posts. It does depend on the server’s own policy — if a server lets the same actor publish the same object more than once, the reference alone does not distinguish a retry from a new publication — but where that policy exists, the submission is idempotent and the server records nothing the second time.
Which produces the one place the draft departs from a MUST. Section 6 requires 201 Created. When the server recorded nothing, 201 asserts a creation that did not happen, so the draft recommends 200 OK with the existing activity’s Location. The same reasoning extends to a Delete naming an object by URI, with the complication that a deletion after an Undo(Delete) is a new transition rather than a retry.
What it deliberately leaves alone. How many Create activities one object may have, and who may publish them. A server may record one and treat its actor as the publishing authority; a server may accept one from each of several actors and call the object jointly published. Both should be able to implement this.
Questions.
- Is a referenced
Createa reasonable client-to-server submission at all, or should publishing an existing object stay out of band? - Is rejecting request-side addressing right, or does someone have a case for merging it?
- Is
200on a recognised retry acceptable as a documented exception, or is there a better answer I have not found? - Has anyone hit this already and solved it differently? I would rather adopt an existing answer than add one.
We have this implemented in a WordPress plugin set and covered by tests; it is not in a released version yet. Draft text follows in the pull request once there is a discussion link to point at.
It’s reasonable. But is there any reason why not consider an Announce activity? It’s perfectly fine to have an object be “created” beforehand and then later announce its existence, even much later.
It’s also possible to send the Create notification with any published date, even a different published date than the object.
An Activity can have different addressing than its object. The addressing of the activity controls where the Activity will be delivered. The addressing of the object might control who can fetch the object. The former should probably be a subset of the latter, and the ActivityPub specification suggests merging the audiences, but they don’t have to be the same.
I would still use a
201 Createdif you are talking about a POST request to the outbox. There, the 201 status indicates that the activity was published, not that the object was created.This might be clarified upstream within the ActivityPub specification soon, if that is a suitable answer to you. https://github.com/w3c/activitypub/issues/438 is on the 1.1 milestone. The idea of what exactly it means to “create” an object within ActivityPub is something that we need to clarify for the next revision of the specification. See also https://github.com/w3c/activitypub/issues/662 for tracking this issue.
Thank you — this is more useful than I expected, and two of the points change the draft.
On
201. Agreed, and I should have been clearer:201refers to the activity being published, not to the object being created. ADeletemakes it sharpest — the201there says aDeleteactivity was recorded, and says nothing about whether the object now answers with aTombstone, a404or a410. Running that sequence out:The second line is the only one I am asking about. The first submission records an activity and gets
201; the question is what to return for a POST the server has recognised as a retry, where it recorded nothing.Section 6 requires
201 Createdfor a submission without qualification, which reads naturally as long as an accepted submission records an activity. RFC 9110 defines201as meaning the request “has resulted in one or more new resources being created”, and defines the content of a200for a POST as “the status of, or results obtained from, the action”. So where a server accepted the submission and recorded nothing, reading Section 6 as covering that case obliges it to say something about its own request that is not true, while the honest answer is a status Section 6 does not mention. That is less a deviation than a case the requirement was not written for, and leaving it undocumented means every server that needs it invents its own answer quietly.It is also not special to a referenced
Create, now that I look at it. Whenever an outbox recognises a submission as a retry and records no new activity, it can answer200and identify the existing one —Follow,LikeandBlockare the familiar cases. Which submissions count as retries is a separate question, and a contested one, asw3c/activitypub#381and#485show; the draft proposes the response for a referencedCreatewithout defining deduplication rules for anything else.There is a practical reason to identify the existing activity rather than say nothing about it: that id is what a client needs later to address an
Undo, which is close to what#485describes senders losing. A C2S response obviously cannot settle how a remote server deduplicates on its side, but it can at least tell its own client which activity is the authoritative one.So: do you mean replaying the original
201andLocationfor that second POST, or something else? I would rather understand the proposal before revising.On
Announce. You are right that the draft never says why not, which is a gap rather than a disagreement — and @steve_bate, this is the answer to your/ccas well.Announceis a reasonable choice when the goal is to draw attention to an object. The goal here is narrower: to record that an actor published this object to the fediverse for the first time. That record is what establishesattributedToand what laterUpdateandDeleteactivities refer back to — which is also whyUpdatecannot be treated the same way, since the same actor may legitimately update the same object many times.Your own observation supports the shape, incidentally: an object may be created beforehand and the
Createsent later, with its ownpublisheddate. That is exactly the case the draft is about. I will add a section on whyCreatehere, and on whenAnnounceis the better fit.On addressing, I was too strict. “The addressing of the activity controls where the activity will be delivered; the addressing of the object might control who can fetch the object; the former should probably be a subset of the latter” states it better than the draft does. What I meant to prevent is a client supplying a narrower audience and the server publishing to the object’s wider one, so the client believes it published narrowly when it did not. Refusing every addressing property also forbids delivering more narrowly, which I did not intend.
What I am less sure of is how a server should check it, because the comparison is not a subset of URI lists. If the object is addressed to
Publicand the request names a single actor, the delivery is narrower in effect even though that actor may appear nowhere in the object’sto. So I would like to allow narrowing and work out the test, rather than write one into the draft today.Lastly,
w3c/activitypub#662looks closely related from the other direction: it asks what a server must do for an object that “is created”. The referenced case may be a useful data point there, because what the server owes the object is nothing — no identifier is assigned and it is already persisted — while it still checks authorisation and records a new activity. Separating those two is what makes the response question appear at all.