flak rss random

activity notes

So you have an ActivityPub actor and you want to say something. What are you going to post? Might I suggest a Note?

A Note is the defacto default object for posts for many microblog programs. There are also some other types, like Article, but even simple Notes have plenty of details and variation to unpack.

The actor object is mostly infrastructure to support the network, but notes are much more user visible. As such, UI and presentation concerns bleed into the object representation.

(I collected a bunch of objects for download, but then I lost them.)


The first observation is that if we receive an object via our inbox, or fetch via somebody’s outbox, it will be wrapped in a Create. Or if we’re sharing, it will be an Announce. The Note will exist either directly inside the object field, or that will be a URL where we can fetch the object.

The Create activity has a number of fields like to and cc which shadow those in the Note. It’s probably best if they agree, but this is not the section about addressing.

Compatibility note: Activity wrapping is somewhere between a curiosity and vitally important for federation. A common mistake is to publish the wrong thing, either with or without activity, in the wrong place. Some software ignores such discrepancies, other software cares a great deal.


Like actors, need to have a @context field to indicate ActivityStreams.


The id field must be a URL where this object can be fetched. (Assuming it’s public.) If you want your posts to federate, don’t screw it up.

Compatibility note: The Create activity also has an id. It should be different from the note’s id, and also globally unique, although it won’t be retrieved.


The url will frequently be the browser friendly URL for a post.

Compatibility note: The url may be the same as id. Or it may be omitted entirely, in favor of the id.

Compatibility note: The url may be an array. PeerTube objects have many urls for different resolutions and formats.


Every note has a type of “Note”. Other types exist, such as Article, Link, Question. Plus more media focused types like Video.

Compatibility note: Every implementation likes its own type, whatever it is, best, and either kinda sorta fakes other types into its native type, or ignores them.

Compatibility note: Note and Article are particularly close, semantically. They could be displayed the same way, although some software does not.


The author of this note. Generally the actor that created it, but not always.

PeerTube specifies an array, containing both the author and channel.

Compatibility note: It’s tempting to treat attributedTo as the author. I just said so. But per the spec, there can be more than one, and they need not be actors.

Compatibility note: The attributedTo field and the actor field from the Create activity should probably match.


There is an official audience field. You probably won’t see it. But there are many other fields which relate to the concept, however.

There are to and cc fields, like in email. Per the spec, they are the same. Either can be a simple string, or an array. They can be actors, but may also be collections (followers).

The magic string "https://www.w3.org/ns/activitystreams#Public" is used to indicate a post is public. It can be in either to or cc fields. If it’s not present, the post is something other than public.

Compatibility note: Software may treat to and cc differently, which may determine where a message appears or whether it generates a notification. How or why, well, probably have to read the source. Nobody documents how their particular UI elements are translated to ActivityPub objects. As long as I interoperate with my implementation, that’s enough, right?

There’s also bto and bcc fields. You might see these in objects sent to you, but support is rare. If you see it in a fetched object, somebody screwed up.

As hinted above, all of these fields can exist in both the Create and Note object. They can differ. Weird stuff. Really, delivery and addressing is a whole topic unto itself.


Finally, something kinda interesting. The content field contains the actual message. By default, it’s some kind of HTML.

Compatibility note: HTML may or may not be scrubbed to varying degrees. For security, you should definitely do some processing to it. You probably don’t need to remove <i> tags however.

Compatibility note: PeerTube uses markdown, and sets mediaType appropriately. Most other software won’t render it, though, instead just displaying the source (even if that program supports markdown in its own composition UI).

Compatibility note: There’s no defined limit to the length of content. Some software allows the posting of some big honking notes.

Next to the content field, there may be a contentMap with a field like en or fr. And then there’s a duplicate of the content.

Compatibility note: Actually sending a Note translated into more languages is likely to result in all but one being ignored. The content map is mostly a verbose way of tagging the post’s single language.


A few comments about presentation of content. “This section is non normative.”

Most implementations don’t display metadata about a post to the user. Addressing, hashtags, etc. This will only be displayed to the end user if it’s included as part of the message content. Thus the origin server that creates a post is responsible for packing up and choosing a suitable presentation.

One consequence is that deviating from other implementations’ behavior (by omitting data from content, for example) results in posts that appear broken on other instances.

A second consequence is that including such metadata elsewhere in the UI will likely lead to duplicated display of information for posts from other instances.

This system is not particularly flexible, and limits innovation, but here we are.

Compatibility note: Many attachments and emoji have transparent backgrounds, but use black as a foreground color and expect it to contrast with the background. So be careful going too dark with the background in the UI.

Compatibility note: Many people compose and view on narrow screens. A screenshot of a phone displayed at native resolution on a desktop looks weird.

Compatibility note: The prevalence of software that doesn’t support HTML composition but autolinks URLs leads to the impression that links like flak.tedunangst.com/post/activity-notes always go where it appears they go. But joinmastodon.org links can deceive. No hove, no love.


Attached to or included with a Note one will commonly find custom emojis, hashtags, and mentions. These all show up in the tag field, but part of the story is also within the content.


Within the text of the content, one may find :emoji_name:. Within the tag field, one may find an emoji with the corresponding name.

      "icon": {
        "type": "Image",
        "url": "https://social.shadowfacts.net/emoji/custom/drake_like.png"
      "id": "https://social.shadowfacts.net/emoji/custom/drake_like.png",
      "name": ":drake_like:",
      "type": "Emoji",
      "updated": "1970-01-01T00:00:00Z"

inlinehtml<img alt="drake like" src="/images/drake_like.png" style="float:left; width:2em; height:2em"> On the receiving server we replace the :drake_like: text with an inline image. Probably want some max size CSS to avoid liking it too much.inlinehtml<p style="clear:both">

Compatibility note: Animated gifs. Your luck may vary.

Compatibility note: Some implementations forbid inline images, but by stringing together a sequence of emoji one can create a mosaic. Not that anybody would do this.


Users involved in a thread have several ways to be associated with it. They can be included in the to and cc fields. It’s also customary to include their handle and a link to their profile within content.

<span class="h-card"><a data-user="9hpDXO5JJQBK7R8BRw" class="u-url mention" href="https://honk.tedunangst.com/u/tedu">@<span>tedu</span></a></span>

The CSS classes are a hint that this is a microformat, and that implementations should skip the link preview. (Otherwise you end up with yet another avatar floating at the bottom of the post.)

Also, there’s a Mention added to the tag field.

      "href": "https://honk.tedunangst.com/u/tedu",
      "name": "@tedu@honk.tedunangst.com",
      "type": "Mention"

Some of this is clearly redundant.

Compatibility note: I’ve seen notes that mentioned people but did not include them in the cc list. At least here, that results in no notification. Results may vary.

Compatibility note: Implementations will generally not attempt to build the recipient list from the links in the content, but that’s also generally the only UI presented to the recipient.


Hashtags aren’t so different from mentions. There’s some inline HTML.

<a href="https://pixelfed.social/discover/tags/alberta?src=hash" title="#alberta" class="u-url hashtag" rel="external nofollow noopener">#alberta</a>

This usually links back to the origin server, because it doesn’t know what server it’s being displayed on.

Then there’s the metadata tag.

      "href": "https://pixelfed.social/discover/tags/alberta",
      "name": "#alberta",
      "type": "Hashtag"

This allows the receiving instance to know which tags apply, and add it to the appropriate collection. So we can visit #mastodev on mastodon.social to keep up with current developments from around the federation.


The summary field can be considered the subject. It’s also used for content warnings and spoilers.

Compatibility note: Some implementations treat it as plain text. Some treat it as HTML.

Compatibility note: Using custom emoji in the subject may work on one instance, but not when others reply.

Compatibility note: PeerTube uses name instead of summary. (Since their objects are Video.)


The sensitive field is a boolean indicating sensitive.

Compatibility note: This usually means only the summary is displayed. Unless there isn’t a summary, and then the content is displayed anyway.


The attachment field is an array of attachments. This is mostly straightforward, except for the surprises.

Each attachment has a type. A common value is the generic “Document”. Other values could be “Image” or “Video”, etc.

Each attachment may have a mediaType. It’s a lie.

Each attachment should definitely have a url or else it’s going to be hard to get, although one could possibly inline it.

Each attachment may have a name although in practice this gets used as a caption.

      "blurhash": "UEN]:y_N_1.8Vr%NM|xst.RnITxByCRjxUM}",
      "mediaType": "image/jpeg",
      "name": "Doge meme with Impact font. Written backwards, it reads, \"How does it feel to be the meme\"",
      "type": "Document",
      "url": "https://cdn.deadinsi.de/media_attachments/files/001/428/015/original/10a10eaf9ecdd3f6.jpeg"

The blurhash is an extension used for preview and image hiding.

Compatibility note: Other than the URL, the other fields are not really trustworthy.

Compatibility note: Immediately after receiving a note, many implementations will attempt to download and cache all the attachments. A small note with a large attachment can result in a surge of traffic to (from) the origin.

Compatibility note: Implementations may transcode media. Mastodon uses ImageMagick, which may result in a cropped image becoming uncropped.


The context field, not to be confused with @context is very helpful for threading. It can contain just about any value, and may not be fetchable on its own.

Compatibility note: Mastodon doesn’t include this field, but includes a functionally identical conversation field. (Holdover from OStatus.)

The context is not always present. Without it, one must use inReplyTo links to try and reconstruct a thread, though this is error prone.


Should be pretty obvious. The inReplyTo field is the id of the object we’re replying to.

Compatibility note: Sometimes one sees something that looks more like a context value (tag: url) in the inReplyTo field. Oh well.


Mastodon will include a replies collection, but it’s only self replies. If you want to know if anybody else replied to a post, in a programmatic manner, this is not it.


A post with a type of “Question” is usually presented as poll. The options and responses are contained in the oneOf field. A multiple choice poll uses anyOf.

  "oneOf": [
      "name": "Yes",
      "replies": {
        "totalItems": 311,
        "type": "Collection"
      "type": "Note"
      "name": "No",
      "replies": {
        "totalItems": 80,
        "type": "Collection"
      "type": "Note"

Replying to a Question to vote on it is another beast entirely.

Compatibility note: The number of poll options may be restricted by some software. Excessive options may simply not appear.

Compatibility note: After adding poll support, all your users will immediately create 10 polls to test the feature, then never use it again after the novelty wears off.


This is probably enough of a headstart to implement support for common features without extensive reversing, but it’s really only an overview of the common stuff. Specific implementations also add tons of other data that only they can interpret, or attach specific semantic meaning to otherwise generic fields.

Posted 17 Jul 2019 19:32 by tedu Updated: 02 May 2022 01:18
Tagged: activitypub web