ActivityPub is a standard for publishing structured social network data on the Web in JSON-LD format. This document describes various methods for discovering the ActivityPub object described by an HTML page, and conversely the HTML page for an ActivityPub object.

This is a draft of the Social Web Incubator Group (SocialCG) Discovery Task Force.

Introduction

ActivityPub is a standard for publishing structured social network data on the Web in JSON-LD format and sharing that data from client to server and from server to server. This document describes several methods for discovering the ActivityPub object described by an HTML page, and conversely the HTML page for an ActivityPub object.

Social data in the ActivityPub model is a "resource" like a person, image, or place. That resource has an ActivityPub JSON-LD representation (if it doesn't, it's not covered by this document!) and may have an HTML representation. The ActivityPub representation and the HTML representations each have an URL -- possibly the same URL.

"Forward discovery" is any process that, given the resource's HTML representation, will return the resource's ActivityPub representation.

There are multiple techniques for forward discovery which will be documented in this report. Some discovery techniques require the full HTML document -- its markup and content. Others only need the URL of the HTML representation.

When a consumer is doing discovery, it can start with the input it has -- document or URL -- and convert to the other input if it needs to.Converting an HTML document to an HTML URL usually requires access to the document's context, like a browser environment. Converting an HTML URL to a document requires fetching the document and parsing it.

"Reverse discovery" is any process that, given the resource's ActivityPub representation, will return the resource's HTML representation.

As with forward discovery, some reverse discovery techniques require the full ActivityPub document -- its JSON properties and content. Others only need the URL of the ActivityPub representation.

When a consumer is doing reverse discovery, it can start with techniques that use the input it has -- document or URL -- and switch to techniques that use the other input only if needed. Converting an ActivityPub document to an ActivityPub URL requires extracting the id property of the object. Converting an ActivityPub URL to a document requires fetching the document and parsing it.

Some resources have a relationship to another resource, which is its author or creator. The author resource has an ActivityPub JSON-LD representation (again, if it doesn't, it's not covered by this document!) and may have an HTML representation.

"Author discovery" is any process that, given the resource's HTML representation, will return the author resource's ActivityPub representation. Author discovery is important because many ActivityPub processes require delivering activities to the author.

There are a few paths for author discovery:

Note that a resource may have an author resource with an ActivityPub representation, but not have its own ActivityPub representation. An example is an article published in a content-management system (CMS) that is ascribed to an actor with an ActivityPub account who wants to receive credit and/or feedback for the work.

As with forward and reverse discovery, the consumer may start with the URL of the resource HTML or the content of the resource HTML.

For some kinds of resources, especially ActivityPub actors, a Webfinger is a more readable alternative to the ActivityPub representation's URL. In these cases, the forward discovery process and author discovery process might include the Webfinger discovery process as an intermediate step:

In this document, the terms "publisher" and "consumer" are used as in Activity Streams 2.0 Core. The terms are extended to include implementations that publish or consume HTML representations of resources with ActivityPub representations.

In this document, we describe several methods of forward discovery, reverse discovery, and author discovery. Different methods are implemented by different publishers and consumers, and have different trade-offs in terms of complexity, performance, and reliability. A section on verification explains how to verify that the discovered information is accurate. The final sections define best practices for publishers and consumers to maximize interoperability and minimize development effort.

Applicable resource types

Unless otherwise specified, the techniques described below can be used with any Activity Streams 2.0 types. The best-defined groups of AS2 types for HTML discovery are actor types:

and digital content types:

Other ActivityPub types are less likely to have their own HTML representations, such as activity types.

Collection types are often better represented by an object they are closely related to. For example, an actor's outbox collection is often provided on the actor's profile page, which is a representation of the actor. Similarly, the likes or replies of an Image object are often provided on the object's page, and don't have an independent HTML representation. That said, this document does not preclude the possibility of HTML representations for collection types.

Motivating user stories

These are some of the user stories that motivate this work.

URLs in examples

This document uses a consistent format for example URLs:

        https://{name}.example/{path}/{type}-{ordinal}{?ext}
      

Where:

The structure used in the examples is merely mnemonic and non-normative. None of the techniques described in this document depend on a particular URL structure, unless otherwise specified.

HTML to ActivityPub

This form of discovery, forward discovery, will identify an ActivityPub JSON-LD resource based on the HTML representation of the object.

URL as input

These discovery techniques require an URL as input. Consumers may start with URLs if they are extracting links from RSS feeds or microblogging content, or when converting from other social networking platform content.

Content negotiation

Content negotiation is a catch-all term for ways of negotiating the representation of a resource through the HTTP protocol. In this document, it will specifically cover proactive negotiation using the Accept header.

Given the URL for an HTML document, such as https://mixed.example/some/path/to/note-1, a consumer could attempt to retrieve the corresponding ActivityPub JSON-LD object using this HTTP request:

          GET /some/path/to/note-1 HTTP/1.1
          Host: mixed.example
          Accept: application/activity+json, application/ld+json, application/json
        

A compliant server may respond with the ActivityPub JSON-LD object in the body of the response:

          HTTP/1.1 200 OK
          Content-Type: application/activity+json

          {
            "@context": "https://www.w3.org/ns/activitystreams",
            "id": "https://mixed.example/some/path/to/note-1",
            "type": "Article",
            "content": "This is a note."
          }
        

This is typically used when the ActivityPub server and the HTML server are implemented in the same software package. Because this has historically been the case for many implementations, some consumers expect this behavior to be the default.

Alternately, the server may respond with a 308 Permanent Redirect to indicate the location of the JSON-LD representation.

          HTTP/1.1 308 Permanent Redirect
          Location: https://mixed.example/different/path/to/note-1.jsonld
        
Content negotiation failure

If the server does not support content negotiation, it may respond with a 406 Not Acceptable status code.

            HTTP/1.1 406 Not Acceptable
            Content-Type: text/plain

            No representation matching this request could be found.
          

Less compliant servers may ignore the Accept header altogether and return the HTML content regardless:

            HTTP/1.1 200 OK
            Content-Type: text/html

            <html>
            <head>
            <title>Note 1</title>
            </head>
            <body>
            <p>This is a note.</p>
          

A more difficult failure mode to detect arises when the server does not support ActivityPub, but does support content negotiation for another JSON format. Such a server returns a 200 OK status code with a JSON object that does not use JSON-LD, or JSON-LD object that does not use the Activity Streams 2.0 vocabulary:

            HTTP/1.1 200 OK
            Content-Type: application/json

            {
              "property": "value",
              "otherProperty": "otherValue"
            }
          

HTTP Link header

The HTTP Link header can be used to indicate an alternative representation of a resource. A consumer can use this header to discover the ActivityPub JSON-LD object for an HTML page.

Given the URL for an HTML document, such as https://html.example/user/test1/article-1, the consumer can use a HTTP HEAD request to get the headers for the resource, which will hopefully include the Link header:

          HEAD /user/test1/article-1 HTTP/1.1
          Host: html.example
        

A compliant server will respond with the headers for the resource:

        HTTP/1.1 200 OK
        Link: <https://ap.example/api/articles/article-1.jsonld>; rel="alternate"; type="application/activity+json"
        

The link header with the alternate relation type, and an ActivityPub-compatible media type, indicates that the ActivityPub JSON-LD object is available at the linked URL.

This can be a very efficient method of discovery, since the consumer does not need to download the entire HTML document and parse its contents.

Servers may also include the Link header in the response to a GET request for the HTML page.

          GET /user/test1/article-1 HTTP/1.1
          Host: html.example
          

A compliant server will respond with the headers for the resource:

        HTTP/1.1 200 OK
        Link: <https://ap.example/api/articles/article-1.jsonld>; rel="alternate"; type="application/activity+json"
        Content-type: text/html

        <html>
        <head>
        ...
        
Link header failure

Some servers may return the full body of the HTML document in response to a HEAD request, without including a Link header.

          HTTP/1.1 200 OK
          Content-type: text/html

          <html>
          <head>
          ...
          

Webfinger

Webfinger is a standard for discovering metadata about a resource identified with an URL. Finding the ActivityPub URL for an actor identified with an acct: URL is well documented in the ActivityPub and Webfinger report. However, Webfinger can be used to find metadata about other resources, including HTML pages with https: URLs.

Given an URL for a document, like https://html.example/group-1.html, a GET request can be made to an URL in the /.well-known/ path of the domain for the URL, as follows:

          GET /.well-known/webfinger?resource=https%3A%2F%2Fhtml.example%2Fgroup-1.html HTTP/1.1
          Host: html.example
        

Note that the /.well-known/webfinger path is fixed and required for Webfinger.

A compliant server will respond with the metadata for the resource:

          HTTP/1.1 200 OK
          Content-Type: application/jrd+json

          {
            "subject": "https://html.example/group-1.html",
            "links": [
              {
                "rel": "alternate",
                "type": "application/activity+json",
                "href": "https://ap.example/api/groups/group-1.jsonld"
              }
            ]
          }
        

Note that unlike other URLs used in the examples in this report, the /.well-known/webfinger path is fixed and required for Webfinger.

The JRD JSON format includes a number of properties, as defined in the Webfinger RFC 7033. The relevant data structure in this example is the object in the links array with the rel property set to alternate and the type property set to application/activity+json, an ActivityPub-compatible media type. The href property of this link is URL of the ActivityPub equivalent for the HTML page.

Webfinger failure

Not all Webfinger-aware servers return JRD documents for https URLs. Others might only return JRD documents for URLs that represent actors, such as registered users.

As with other link-relation-based discovery mechanisms, like the HTTP Link header or the <link> element, a JSON or JSON-LD media in the link's type property might not indicate an ActivityPub URL, but some other JSON or JSON-LD object.

Document as input

Alternately, a consumer may start with the full contents of an HTML document, including markup and other content. For example, a browser-based application may have access to the HTML loaded in the browser window. It's also usually possible to extract the URL from the environment -- for example, using the document.location property in a JavaScript environment. But using the document content for discovery can return the ActivityPub equivalent without the HTTP requests that discovery by URL requires, saving some time and network traffic.

HTML <link> element

The link element is a metadata element used in the <head> section of an HTML document. It provides links for the whole document, using a number of different link relations.

To indicate its equivalent ActivityPub object, the HTML page at https://html.example/watch/video-1.html could include the following link element:

          <!doctype html>
          <html>
            <head>
              <title>Video 1</title>
              <link
                rel="alternate"
                type="application/activity+json"
                href="https://ap.example/api/descriptors/video-1.jsonld" />
            </head>
            <body>
              <!-- rest of the page -->
            </body>
          </html>
        

Consumers need to parse the HTML to find the link element with the alternate relation and an ActivityPub-compatible media type as type. This can be slow and complicated.

Link element failure

Some servers may include a link element with an alternate relation and with a JSON type or JSON-LD type that does not link to an ActivityPub resource.

            <!doctype html>
            <html>
              <head>
                <title>Video 1</title>
                <link
                  rel="alternate"
                  type="application/json"
                  href="https://api.example/unrelated/videodescriptor.json" />
              </head>
              <body>
                <!-- rest of the page -->
              </body>
            </html>
          

HTML <a> element

The a element is an element used in the <body> section of an HTML document. It can be used to define relationships with other documents, with the benefit that the link is (usually) visible and clickable by a reader.

To indicate its equivalent ActivityPub object, the HTML page at https://html.example/profiles/person-1.html could include the following a element:

          <!doctype html>
          <html>
            <head>
              <title>Person 1</title>
            </head>
            <body>
              <a
                rel="alternate"
                type="application/activity+json"
                href="https://ap.example/users/person-1.jsonld" >
                Actor data for Person 1
              </a>
              <!-- rest of the page -->
            </body>
          </html>
        

Consumers will need to parse the HTML to find the a element with the alternate relation and an ActivityPub-compatible media type as type. This can be even more slow and complicated than with the link header. The link header is usually in the first few kilobytes of a document, and will usually be nested only 2 levels below the document in the DOM tree. An a element may be anywhere in the body, maybe nested very deep in the tree.

a element failure

As with the link element, some servers may include an a element with an alternate relation and with a JSON type or JSON-LD type that does not link to an ActivityPub resource.

In addition, many content management systems allow end users to set rel and other properties on a elements, which may result in false matches. Even more than with other methods, using the a element for discovery requires reverse discovery for confirmation (see Best practices for consumers).

Embedded JSON-LD

HTML documents can include JSON-LD data in a <script> element in the <head> section of the document. This data can be used to provide metadata about the document, including its equivalent ActivityPub object.

Given a page that shows an image at https://html.example/gallery/image-17.html, the HTML for the page could look like this:

          <!DOCTYPE html>
          <html lang="en">
            <head>
              <title>Image 17</title>
              <script type="application/ld+json">
              {
                "@context": "https://www.w3.org/ns/activitystreams",
                "type": "Image",
                "id": "https://ap.example/api/images/image-17.jsonld",
                "url": [
                  {
                    "type": "Link",
                    "mediaType": "text/html",
                    "href": "https://html.example/gallery/image-17.html"
                  }
                ]
              }
              </script>
            </head>
            <body>
                <h1>Image 17</h1>
                <p><img src="https://html.example/images/image-17.png"></p>
            </body>
          </html>
        

This embedded JSON-LD specifies that an ActivityPub object with the ID https://ap.example/api/images/image-17.jsonld exists, and that it has an HTML page url at https://html.example/gallery/image-17.html, that is, the current page's URL. This is a roundabout, but clear, way to specify the ActivityPub ID of the current page.

Consumers need to parse the HTML page, and the embedded JSON-LD, to extract the ActivityPub object ID. An advantage to this technique is that other properties of the ActivityPub object can be embedded as well; however, to confirm those properties, the consumer will need to fetch the object from its canonical URL, the ID, anyways.

Embedded JSON-LD failure

Complicated structures for the url property may make it hard to confirm that the object's URL is the same as the current page's.

Embedded JSON-LD is very popular for embedding Schema.org metadata. This can lead to false positives when looking for ActivityPub objects.

ActivityPub to HTML

Reverse discovery, in this report, means identifying the HTML page that represents the same object as an ActivityPub JSON-LD object. This is necessary for user stories like creating a link to an actor in microsyntax.

URL as input

These techniques require the ActivityPub object's URL as input. Often, the object's URL is obtained either as a property of another ActivityPub object, or from the id property of the ActivityPub JSON-LD document.

If these techniques aren't successful, the consumer can use the URL to fetch the ActivityPub JSON-LD document, and then use a reverse discovery technique that takes a document as input.

Content negotiation

As with forward discovery, it's possible for the HTML and JSON-LD representations of an object to be found at the same URL.

Examples

Given the URL for an ActivityPub object, such as https://mixed.example/some/path/to/note-1, a consumer could attempt to retrieve the corresponding HTML resource using this HTTP request:

            GET /some/path/to/note-1 HTTP/1.1
            Host: mixed.example
            Accept: text/html
          

A compliant server may respond with the HTML document in the body of the response:

            HTTP/1.1 200 OK
            Content-Type: text/html

            <html>
            <head>
            <title>Note 1</title>
            </head>
            <body>
            <p>This is a note.</p>
          

Alternately, the server may respond with a 308 Permanent Redirect to indicate the location of the HTML representation.

            HTTP/1.1 308 Permanent Redirect
            Location: https://mixed.example/different/path/to/note-1.html
          
Content negotiation failure

If the server does not support content negotiation, it may respond with a 406 Not Acceptable status code.

            HTTP/1.1 406 Not Acceptable
            Content-Type: text/plain

            No representation matching this request could be found.
          

Less compliant servers may ignore the Accept header altogether and return the JSON-LD content regardless:

            HTTP/1.1 200 OK
            Content-Type: application/activity+json

            {
              "@context": "https://www.w3.org/ns/activitystreams",
              "id": "https://mixed.example/some/path/to/note-1",
              "type": "Note",
              "content": "This is a note."
            }
          

HTTP Link header

As with forward discovery, it is possible to use the Link header to identify an HTML page related to a given ActivityPub JSON-LD resource. A Link header with the alternate link relation and a type equal to text/html indicates an HTML page representing the same object.

The advantage of this technique is that it does not require downloading and parsing the JSON-LD content of the ActivityPub object. The Link header has fewer options for formatting than other methods such as the url property, for example, making it slightly easier for consumers.

Example

Given an ActivityPub JSON-LD object at https://ap.example/some/path/person-1.jsonld, a consumer could use a HEAD HTTP request to get the relevant headers for the resource:

          HEAD /some/path/person-1.jsonld HTTP/1.1
          Host: ap.example
        

The publisher would respond with the HTTP headers, including a Link header:

          HTTP/1.1 200 OK
          Content-Type: application/activity+json
          Link: <https://html.example/profiles/person-1.html>; rel="alternate"; type="text/html"
        
HTTP Link header failure

Some non-compliant HTTP servers will send the full body of the resource in the response to the HEAD request.

Webfinger

The Webfinger protocol can be used to find an HTML page related to an ActivityPub object in a number of ways.

The consumer can identify the resource for the a Webfinger query in two ways. First, the id property, usually an https URL, can be passed as the resource parameter for the Webfinger query. Alternately, if the ActivityPub object is an actor, an acct URL in the format acct:username@domain.example can be constructed using the technique for Webfinger reverse discovery. This acct URL can be used as the resource parameter for the Webfinger query.

The publisher can provide a link to the HTML representation of the object in the JRD output of the Webfinger query in at least two ways.

First, the links property of the output object can contain a link object with a rel property set to alternate and the type property set to text/html. If such a link exists, its href property is the URL of the related HTML page.

Second, the links property of the JRD output object may include an object with a rel property set to http://webfinger.net/rel/profile-page. This is defined to be "the main home/profile page that a human should visit when getting info about that webfinger account." (https://webfinger.net/rel/) It is not guaranteed to be HTML, but a type property can further define that. Per the definition, "it's likely text/html if it's for users."

An advantage of using Webfinger for discovery is that it is widely implemented by ActivityPub publishers to enable using acct URLs as identities.

Examples

Given an ActivityPub Place object at https://ap.example/geo/place-7.jsonld, a consumer could use a Webfinger query to find the HTML page for the object:

            GET /.well-known/webfinger?resource=https%3A%2F%2Fap.example%2Fgeo%2Fplace-7.jsonld HTTP/1.1
            Host: ap.example
          

Note that the /.well-known/webfinger path is fixed and required for Webfinger.

The publisher could return the following JRD output:

            {
              "subject": "https://ap.example/geo/place-7.jsonld",
              "links": [
                {
                  "rel": "alternate",
                  "type": "text/html",
                  "href": "https://html.example/map/nl/ams/17921.html"
                }
              ]
            }
          

In this example, the links property of the JRD object contains a single object with a rel property set to alternate and a type property set to text/html. The href property of this object is the URL of the HTML page representing the object.

Alternately, given an ActivityPub Person object at https://ap.example/profiles/person-19.jsonld, the consumer could construct an acct URL as acct:person-19@ap.example and use it as the resource parameter for the Webfinger query:

            GET /.well-known/webfinger?resource=acct%3Aperson-19%40ap.example HTTP/1.1
            Host: ap.example
          

Note that the /.well-known/webfinger path is fixed and required for Webfinger discovery.

The publisher could return the following JRD output:

            {
              "subject": "acct:person-19@ap.example",
              "links": [
                {
                  "rel": "http://webfinger.net/rel/profile-page",
                  "type": "text/html",
                  "href": "https://html.example/profiles/person-19.html"
                }
              ]
            }
          

In this output, the http://webfinger.net/rel/profile-page relationship identifies an HTML page for the Person object.

Webfinger failure

Some servers may not return JRD documents for https URLs. Others might only return JRD documents for URLs that represent actors, such as registered users.

Document as input

These techniques require the ActivityPub JSON-LD document as the input for the process. The document can be obtained through delivery via the ActivityPub protocol, or through the ActivityPub API, or by other means.

If none of these techniques are successful, the consumer can obtain the URL of the object from the id property, and then try one or more of the techniques that require an URL.

url property

ActivityPub objects can have an optional url property, which "[i]dentifies one or more links to representations of the object." The property is the preferred way to indicate a corresponding HTML page for an ActivityPub object.

As with many Activity Vocabulary properties, this can have several formats:

  • A string. In this case, the string is the URL itself.
  • A Link object. This structure is used to provide additional information about the link, including the mediaType. For an equivalent HTML representation, the mediaType property will be "text/html". The href property of the Link object is the URL.
  • An array: One or more strings and/or Link objects.
Examples

In this example, the url property is only a string.

          {
            "@context": "https://www.w3.org/ns/activitystreams",
            "id": "https://ap.example/some/path/person-1.jsonld",
            "type": "Person",
            "name": "Person One",
            "url": "https://html.example/profile/person-1.html"
          }
          

In the next example, the url property is a full Link-type object with mediaType property equal to "text/html".

          {
            "@context": "https://www.w3.org/ns/activitystreams",
            "id": "https://ap.example/geo/place-17.jsonld",
            "type": "Place",
            "nameMap": {
              "en": "Berlin"
            },
            "url": {
              "type": "Link",
              "mediaType": "text/html",
              "href": "https://html.example/map/de/ber/ber.html"
            }
          }
          

In this final example, the url property is an array of Link-type objects with different mediaType properties.

          {
            "@context": "https://www.w3.org/ns/activitystreams",
            "id": "https://ap.example/photos/gallery/image-3.jsonld",
            "type": "Image",
            "summary": "Jason and Carol at the lake house",
            "url": [
              {
                "type": "Link",
                "mediaType": "text/html",
                "href": "https://html.example/gallery/3.html"
              },
              {
                "type": "Link",
                "mediaType": "image/webp",
                "href": "https://upload.example/files/08/17/2021/lakehouse.webp"
              }
            ]
          }
          
url property failure

When the url property is only a string, it may not represent an HTML page. Especially for objects with binary content types, like Image, Video, and Audio, the url property is often used for the URL of the respective binary representation of the object.

The mediaType of Link-type objects in the url property is not always defined, and when it is defined, it is not always "text/html".

The property is defined only for representations of the current object. However, the Link-type object can have a link relation property, rel. Publishers may misuse the url property to including links that aren't a representation of the object, but instead a related object, like "next" or "author".

HTML to author ActivityPub

This section describes various methods for determining the ActivityPub ID of the author of an object represented by an HTML page.

Discover equivalent object

One way to discover the author of an object represented by an HTML page is to first discover the object's ActivityPub JSON-LD representation, then use that information to determine the author.

Any of the methods in the forward discovery section can be used to find the URL of the ActivityPub JSON-LD resource. Fetching the resource will retrieve the full JSON-LD representation of the object.

Three main properties are used, in ActivityPub, to identify the author of an object:

Each of these properties can have values that are a string, a JSON object, or a JSON array consisting of strings and/or JSON objects. If the value is a string, it is the URL of the author object. If the value is an object, its id property is the URL of the ActivityPub JSON-LD object. For an array, each item can be resolved as either a string or an object.

The advantage of this method is that it does not require a separate discovery path for authors.

Example

Given an HTML page at https://html.example/note-1.html that represents a Note object, the consumer can use an HTML <link> header to identify the URL of the ActivityPub note:

          <link
            rel="alternate"
            type="application/activity+json"
            href="https://ap.example/api/notes/note-1.jsonld" />
        

Fetching the content at https://ap.example/api/notes/note-1.jsonld will return the JSON-LD representation of the note:

          {
            "@context": "https://www.w3.org/ns/activitystreams",
            "id": "https://ap.example/api/notes/note-1.jsonld",
            "type": "Note",
            "content": "This is a note.",
            "attributedTo": {
              "id": "https://ap.example/profiles/person-1.jsonld",
              "type": "Person",
              "name": "Person One"
            }
          }
        

In this example, the attributedTo property is an object with an id property that is the URL of the author object.

Object discovery failure

This discovery method can fail if the ActivityPub object requires authentication to be fetched -- which is often the case for content that was not made available to the public by the author. A simple consumer may not have the context necessary to use OAuth 2.0, HTTP Signature, or other authentication methods.

Not all ActivityPub implementations include the attributedTo or actor property for an object in its default representation.

URL as input

This section describes techniques for discovering an ActivityPub author object using the HTML representation's URL as an input. The HTML representation's URL may come from ActivityPub properties, links in other content, or by other means.

If these techniques do not succeed, consumers can fetch the content of the HTML document and use one of the techniques that require HTML as input.

HTTP Link header

The Link header can also be used to identify the author of an HTML page. The author link type identifies the author of a resource. So, a Link header with a rel property equal to "author" and a type property equal to "application/activity+json" will link to the ActivityPub representation of the author of the object.

This technique has the advantage of requiring a minimum amount of data transfer and complicated parsing of data structures. It can also be used with different media types than just HTML; for example, a JPEG image.

Example

Given an HTML page at https://html.example/files/video-33.html that represents a Video object, the consumer can use an HTTP HEAD request to identify the URL of the ActivityPub video:

            HEAD /files/video-33.html HTTP/1.1
            Host: html.example
          

The publisher would respond with the HTTP headers:

            HTTP/1.1 200 OK
            Content-Type: text/html
            Link: <https://ap.example/api/videos/video-33.jsonld>; rel="alternate"; type="application/activity+json"
            Link: <https://ap.example/profiles/person-7.jsonld>; rel="author"; type="application/activity+json"
          

Note that there are two Link headers in the result; one represents the Video, and the other represents the Person that authored the video. The rel value of "author" distinguishes the author link.

Link header failure

Some non-compliant publishers will respond to a HEAD request with the full body of the HTML document.

Document as input

These techniques require the contents of the HTML representation. This could already be available in a browser environment, or through other means.

If these techniques don't succeed, it's sometimes possible to obtain the URL of the document through the environment, for example, using the document.location property in a browser JavaScript program. Consumers can then try techniques that use the URL as input.

HTML <link> element

The ActivityPub id of the author of an object represented by an HTML document can also be found using the HTML <link> element. As with the HTTP Link header, if a <link> element has a rel attribute equal to "author" and a type attribute equal to "application/activity+json", its href property is the URL of the ActivityPub object representing the author.

This technique is useful when the HTML page is already downloaded and available, such as within a browser environment.

Example

Given an HTML page at https://html.example/files/document-40.html that represents a Document object, the consumer can use the following HTML to identify the URL of the ActivityPub representation of the document:

            <!doctype html>
            <html>
              <head>
                <title>Document 33</title>
                <link
                  rel="author"
                  type="application/activity+json"
                  href="https://ap.example/profiles/person-7.jsonld" />
              </head>
              <body>
                <!-- rest of the page -->
              </body>
            </html>
          

In this example, the <link> element with the rel attribute set to "author" and the type attribute set to "application/activity+json" is the author of the document.

Link element failure

Some servers may include a <link> element with an author relation and with a JSON type or JSON-LD type that does not link to an ActivityPub resource.

OpenGraph Protocol

The OpenGraph Protocol is a set of metadata properties that can be included in an HTML document to provide information about the document. Several of these properties can be used to identify the author of the document; usually with the URL of an HTML page that has a profile type.

  • music:musician identifies the creator of a song or album.
  • music:creator identifies the creator of a playlist or station.
  • video:actor identifies the actor in a video.
  • video:director identifies the director of a video.
  • video:writer identifies the writer of a video.
  • article:author identifies the author of an article.
  • book:author identifies the author of a book.

For these properties, the most likely discovery path is to follow the property's URL value to an HTML page for the profile, and then use forward discovery to find the ActivityPub object corresponding to that profile.

Another option is the fediverse:creator property, developed by Mastodon. This property is used to identify the creator of a document. Its value is the Webfinger ID of the creator, prefixed with an @ character, not an acct: URL. The consumer can use the Webfinger protocol to find the ActivityPub object corresponding to the creator.

The advantage of using OGP for metadata is that it is widely implemented by Web publishing systems.

Example

Given an HTML page at https://html.example/files/article-40.html that represents a Article object, the consumer can use the OpenGraph metadata to identify the URL of HTML profile for the author of the article:

            <!doctype html>
            <html>
              <head>
                <title>Article 40</title>
                <meta property="article:author" content="https://html.example/profiles/person-7.html" />
              </head>
              <body>
                <!-- rest of the page -->
              </body>
            </html>
          

In this example, the article:author property is the author of the document. (Note that OpenGraph Protocol uses the non-standard property attribute for metadata.)

Given an HTML page at https://html.example/files/video-40.html that represents a Video object, the consumer can use OpenGraph metadata to identify the Webfinger ID of the creator of the video:

            <!doctype html>
            <html>
              <head>
                <title>Video 40</title>
                <meta property="fediverse:creator" content="@person-22@ap.example" />
              </head>
              <body>
                <!-- rest of the page -->
              </body>
            </html>
          

In this example, the fediverse:creator property is the Webfinger ID of the creator of the video, prefixed with the @ symbol. The Webfinger ID can be used to find the ActivityPub object corresponding to the creator.

OpenGraph Protocol failure

The OpenGraph Protocol is not widely used for identifying authors of documents. When it is used, the profile page may not support discovery of an ActivityPub object.

Discovering author HTML

Another option for author discovery is to discover the HTML profile page for the author of the resource, and then discover the equivalent ActivityPub object for the HTML profile page.

Techniques for discovering author HTML pages are out of scope for this report, but may include using HTTP Link headers, <link> elements, <a> elements, or Webfinger link objects with the rel property set to "author" and the type property set to "text/html" or omitted. Embedded JSON-LD, OpenGraph Protocol, and other mechanisms exist for discovering author profile HTML pages.

Example

Given the contents of an HTML page at https://html.example/files/video-40.html that represents a Video object, the consumer can check the <link> elements in the HTML to find the URL of the author's profile page:

          <!doctype html>
          <html>
            <head>
              <title>Video 40</title>
              <link
                rel="author"
                type="text/html"
                href="https://html.example/profiles/person-22.html" />
            </head>
            <body>
              <!-- rest of the page -->
            </body>
          </html>
        

It could then use a forward discovery technique, such as HTTP Link header discovery, to get the equivalent ActivityPub object.

          HEAD /profiles/person-22.html HTTP/1.1
          Host: html.example
        

The response might include a Link header with the URL of the equivalent ActivityPub object:

          HTTP/1.1 200 OK
          Content-Type: text/html
          Link: <https://ap.example/profiles/person-22.jsonld>; rel="alternate"; type="application/activity+json"
        

Author HTML failure

Author HTML discovery can fail if the author's profile page is not available, or if the profile page does not link to an ActivityPub object.

Verification

Publishers of HTML representations and ActivityPub representations include data or metadata to help with discovery of related representations or resources. This data is a claim that the linked resource really has the relationship stated.

Unfortunately, not all claims are true. Consumers need to verify the claims made by publishers, using the verification techniques described here. Some techniques are direct and can be used with confidence; others are heuristics that provide some level of support to the claims, but are not foolproof.

Representation verification

Verification is a process of confirming that an HTML page and an ActivityPub object represent the same resource. This is necessary to ensure that the publisher of one representation is not falsely connecting two unrelated resources. These forms of verification are valid for both forward and reverse discovery.

Two-way discovery

The most reliable verification method is two-way discovery. This consists of first doing discovery in one direction, and then doing discovery in the other direction with the results. For example, doing forward discovery from an HTML page to an ActivityPub object, and then doing reverse discovery from the ActivityPub object to, hopefully, the same HTML page.

Example

Given an HTML page with the URL https://html.example/downloads/image-14.html, the consumer could discover the ActivityPub JSON-LD URL using the Link header method:

            HEAD /downloads/image-14.html HTTP/1.1
            Host: html.example
          

The publisher would respond with the HTTP headers:

            HTTP/1.1 200 OK
            Content-Type: text/html
            Link: <https://ap.example/api/images/image-14.jsonld>; rel="alternate"; type="application/activity+json"
          

Then, the consumer could fetch the ActivityPub object at https://ap.example/api/images/image-14.jsonld and look for the URL of the HTML page in the url property:

            {
              "@context": "https://www.w3.org/ns/activitystreams",
              "id": "https://ap.example/api/images/image-14.jsonld",
              "name": "Image 14",
              "type": "Image",
              "url": "https://html.example/downloads/image-14.html"
            }
          

By comparing the URL in the ActivityPub object with the original HTML page URL, the consumer can confirm that the two representations relate to the same resource.

Two-way discovery failure

Two-way discovery can fail if the discovered representation does not have a path back to the origin representation.

Two-way discovery can become complicated if more than one discovered representation is found, which may connect back to more than one original representation. Verifying multiple relationships, and ignoring those that cannot be verified, complicates this process for the consumer.

Same origin

Another mechanism for verifying a discovery process is to compare the origins of the original representation's URL and the discovered representation's URL. The origin is the combination of the scheme, host, and port of a URL. If the origins are the same, the two representations can be considered related.

Content negotiation without a redirect will always have the same origin, as the URL of the HTML page and the URL of the JSON-LD representation are the same.

Example

Given an HTML page with the URL https://mixed.example/profiles/person-3, the consumer could discover the ActivityPub JSON-LD URL using the <link> element method:

            <link
              rel="alternate"
              type="application/activity+json"
              href="https://mixed.example/api/person/person-3" />
          

The href property of the <link> element is the URL of the ActivityPub JSON-LD representation. The origin of the URL of the HTML page is https://mixed.example, and the origin of the URL of the ActivityPub JSON-LD representation is https://mixed.example, so the origins match and the discovery is verified.

Same origin failure

Same origin verification assumes that a single publisher controls an entire domain. Although this is often true for machine-readable formats like JSON-LD, having multiple publishers in control of parts of a domain is more common for HTML documents. For example, documents with URLs starting with https://html.example/home/user1/ might be created by one user, and those starting with https://html.example/home/user2/ might be created by another. A carefully crafted <link> or other mechanism could be used by one user to link their HTML page to an ActivityPub object created by another.

Same origin verification will give a false negative if the publisher is using different domains for HTML pages and ActivityPub JSON-LD objects. This can happen if ActivityPub features are added on to an existing published Web site, or if the publisher needs to keep the domains separate for implementation reasons. If same origin verification gives a negative result, other methods such as two-way verification should be used.

Allowlist

Another means of verification, or more precisely an excuse for skipping verification, is an allowlist. This is a list of origins or, possibly, other properties of the representation that can be used to confirm trust in the publisher and skip verification.

Assuming that each origin is controlled by a single publisher, if the consumer trusts the publisher, they can skip verification of discovery when the original representation has an URL with that origin.

Example

Given an HTML page with the URL https://html.example/profiles/person-3, the consumer could discover the ActivityPub JSON-LD URL using the Embedded JSON-LD method:

            <script type="application/ld+json">
            {
              "@context": "https://www.w3.org/ns/activitystreams",
              "id": "https://ap.example/api/person/person-3",
              "type": "Person",
              "name": "Person Three"
              "url": "https://html.example/profiles/person-3"
            }
            </script>
          

Given that the consumer trusts the publisher of https://html.example, they can skip verification of the discovery process, and accept https://ap.example/api/person/person-3 as the ActivityPub JSON-LD representation's URL.

Allowlist failure

Maintaining an allowlist is time-consuming. The number of domains with Web sites is in the hundreds of millions; identifying even a tiny fraction to be trusted takes a lot of human effort.

Depending on allowlists as the only means of verification severely limits the number of domains that can be interacted with.

Author verification

Verification of author discovery is necessary to ensure that attackers cannot maliciously ascribe content to an actor that did not create it.

Outbox verification

Unfortunately, the only current way to fully verify the authorship of an HTML object is by scanning the outbox property of the actor object. With tens or hundreds of thousands of items in the outbox not unusual, this is a time-consuming process that is subject to possible errors.

outbox properties in ActivityPub are OrderedCollection objects, often with OrderedCollectionPage objects that represent pages of content. Scanning this collection from newest to oldest members, the consumer can look for Create activities with the url property set to the HTML representation URL being verified, or for activity objects with the url property set to the HTML representation being verified.

Example

The consumer has discovered that https://ap.example/user/person-6.jsonld is the author of the resource represented by the HTML document at https://html.example/blog/article-9.html. The consumer retrieves the ActivityPub JSON-LD for the person:

          {
            "@context": "https://www.w3.org/ns/activitystreams",
            "id": "https://ap.example/user/person-6.jsonld",
            "type": "Person",
            "name": "Person Six",
            "inbox": "https://ap.example/user/person-6/inbox",
            "outbox": "https://ap.example/user/person-6/outbox",
            "following": "https://ap.example/user/person-6/following",
            "followers": "https://ap.example/user/person-6/followers",
            "liked": "https://ap.example/user/person-6/liked"
          }
          

The consumer then fetches the URL that is the value of the outbox property:

          {
            "@context": "https://www.w3.org/ns/activitystreams",
            "id": "https://ap.example/user/person-6/outbox",
            "type": "OrderedCollection",
            "totalItems": 3803,
            "first": "https://ap.example/user/person-6/outbox/page/39"
          }
          

It fetches the first page of the collection:

          {
            "@context": "https://www.w3.org/ns/activitystreams",
            "id": "https://ap.example/user/person-6/outbox/page/39",
            "type": "OrderedCollectionPage",
            "partOf": "https://ap.example/user/person-6/outbox",
            "next": "https://ap.example/user/person-6/outbox/page/40",
            "items": [
              {
                "type": "Create",
                "actor": "https://ap.example/user/person-6.jsonld",
                "object": {
                  "id": "https://ap.example/object/article-10.jsonld",
                  "type": "Article",
                  "name": "Article Ten",
                  "url": "https://html.example/blog/article-10.html"
                }
              },
              {
                "type": "Create",
                "actor": "https://ap.example/user/person-6.jsonld",
                "object": {
                  "id": "https://ap.example/object/article-9.jsonld",
                  "type": "Article",
                  "name": "Article Nine",
                  "url": "https://html.example/blog/article-9.html"
                }
              },
              {
                "type": "Create",
                "actor": "https://ap.example/user/person-6.jsonld",
                "object": {
                  "id": "https://ap.example/object/article-8.jsonld",
                  "type": "Article",
                  "name": "Article Eight",
                  "url": "https://html.example/blog/article-8.html"
                }
              }
            ]
          }
          

The second object in the items array is a Create activity with an object property with a value that includes an url property with same value as the HTML URL we are trying to verify. Since the claim from the HTML and the ActivityPub JSON-LD support each other, the relation is verified.

Outbox author verification failure

This method is error-prone and depends on fetching hundreds or thousands of pages. Not all pages will include the full representation of the objects in their items array; some may just include the id property of each, requiring even more requests.

Some authors may not include a Create activity for every object they create on the Web. They may use an author discovery process to identify the author, but not include the object in the ActivityPub representation.

Same origin verification

This method, or heuristic, assumes that the claims of authorship are mutually supporting if the URLs of the representations have the same origin. The origin of an URL includes its protocol, domain name, and port number. The method assumes that the same entity (like a person or organization) controls all URLs published with this origin, and therefore would not make claims to contradict itself.

Example

Given an HTML page with the URL https://mixed.example/profiles/person-3.html, the consumer could discover the ActivityPub JSON-LD URL using the Embedded JSON-LD method with the value https://mixed.example/api/person-3.jsonld. Because both URLs have the origin https://mixed.example, the consumer will assume that the discovery is verified.

Same origin verification failure

Assuming that the same entity controls creation of all URLs on a server is somewhat risky. For HTML creation, especially, some servers divide up into per-user paths. Other servers allow user-uploaded data, including JSON and HTML.

Allowlist verification

Consumers may include a list of origins or other properties of the representation that don't require verification. This assumes an externally-established trust relationship.

Example

Given an HTML page with the URL https://html.example/profiles/person-3.html, the consumer could discover the ActivityPub JSON-LD URL using the Embedded JSON-LD method with the value https://ap.example/api/person-3.jsonld. If the consumer trusts the publisher of https://html.example, they can skip verification of the discovery process, and accept https://ap.example/api/person-3.jsonld as the ActivityPub JSON-LD representation's URL.

Allowlist verification failure

Establishing trust relationships out-of-band is labor intensive, and most consumers will only have a small number of trusted domains or other entities.

Best practices for consumers

This section describes some best practices for consumers of HTML and ActivityPub.

Discovery techniques

Forward discovery

Consumers with the HTML representation's URL as input should try these techniques:

  • HTTP Link.This is a fast and easy way to identify a related ActivityPub object. It will work whether the HTML and ActivityPub representations are on the same server or on different ones. It will also work with binary media types like images or videos.
  • Content negotiation. For servers that implement both the HTML and ActivityPub representations of the object, this is an easy method to use. It will not work for ActivityPub implementations assembled from different server systems, so it should be supplemented with other methods.

Consumers with the HTML document as input should try these techniques:

  • HTML <link>. This element is somewhat easier to fetch than others, and requires no further parsing. It's always at the second level of element hierarchy, below the head element.
  • HTML <a>. This element is often buried deep in the element tree of an HTML document.

If discovery with the URL as input is unsuccessful, fetching the document may provided better information. Similarly, if discovery with the document does not succeed, obtaining the URL and using it for discovery may work.

Consumers with rigourous discovery requirements, like indexers or search engines, can try additional discovery methods like Webfinger or embedded JSON-LD. However, it's unlikely that publishers that haven't implemented one of the above methods would implement more obscure methods.

Reverse discovery

Consumers with the ActivityPub representation's URL as input should try the following techniques:

  • HTTP Link This method is simple, doesn't require a lot of parsing, and works even if the HTML and ActivityPub representations are on different servers.
  • Content negotiation. Many servers that implement both the ActivityPub and HTML representations of a resource use this method.

Consumers with the ActivityPub document as input should try these techniques:

  • url property. This is a straightforward method that is defined within the Activity Streams 2.0 specification.

If these techniques are exhausted, using the other type of input is a good next step.

Other discovery techniques are unlikely to be used by publishers if these are not.

Author discovery

Consumers with the URL of HTML representation as input should start with these techniques:

  • HTTP Link. Similar to forward discovery, this is a very quick and easy way to identify a related author when an URL is available. It will work for several types of server configuration.

Consumers with the HTML document as input should try these techniques:

  • HTML <link>. There's a long tradition of using this property for linking metadata. It's also easy to parse with the DOM, and doesn't require using queries or deep traversal of element trees.
  • HTML <a>. This is an easy property for content management system users to set directly.
  • OpenGraph. The OpenGraph properties, like fediverse:creator, are easy to fetch from the head of the document.

Failing these, the next best option is to discover the ActivityPub representation of the resource, and then use ActivityPub properties to discover the author.

More demanding consumers may want to continue with discovery of the author's HTML profile page, but this can be a complicated process.

Verification

Consumers should do verification of forward and reverse discovery.

Two-way discovery is the most reliable way to verify results, but can have resource overhead.

Same-origin verification can be reliable for reverse discovery, but is less so for forward discovery or author discovery.

Allowlists are a last resort when other verification is not possible.

Best practices for publishers

Publishers that want consumers to be able to discover ActivityPub object and their authors should consider these methods.

Publishing ActivityPub JSON-LD

When publishing an Activity Streams 2.0 JSON-LD object for ActivityPub, publishers should consider these best practices.

Publishing HTML

When publishing HTML representations of an ActivityPub resource, include these discovery options: